diff --git a/PIPELINE_GUIDE.md b/PIPELINE_GUIDE.md new file mode 100644 index 00000000..0f549437 --- /dev/null +++ b/PIPELINE_GUIDE.md @@ -0,0 +1,452 @@ +# DronePlanning 处理 Pipeline 说明 + +本文档说明当前后端从自然语言输入到计划 JSON 输出的完整处理流程,以及可自定义修改点。 + +## 1. 总体流程 + +入口为 `POST /generate_plan`,主链路由 `GenerationOrchestrator` 编排: + +```mermaid +flowchart LR +api[main.py /generate_plan] --> gen[py_tree_generator.generate] +gen --> orch[GenerationOrchestrator] +orch --> s1[Stage1 TaskUnderstanding] +orch --> s2[Stage2 ContextBinding] +orch --> s3[Stage3 BTPlanning] +orch --> s4[Stage4 ValidateAndPostprocess] +s4 --> out[返回 py_tree JSON] +``` + +对应代码: + +- `backend_service/src/main.py` +- `backend_service/src/py_tree_generator.py` +- `backend_service/src/pipeline/orchestrator.py` +- `backend_service/src/pipeline/stages.py` + +## 2. Stage1:任务理解(TaskUnderstanding) + +功能: + +- 场景分类:`simple / scene1 / scene4` +- 意图推断:`intent_type` +- 风险标记:`risk_flags`(例如是否需要人工确认、是否涉及相对方位) + +核心代码: + +- `backend_service/src/llm/gateway.py::classify_scene()` +- `backend_service/src/pipeline/stages.py::_infer_intent_type()` +- `backend_service/src/pipeline/stages.py::_extract_risk_flags()` +- 数据契约:`backend_service/src/pipeline/contracts.py::TaskUnderstanding` + +说明: + +- 分类模型可启用 thinking(由 `STAGE1_ENABLE_THINKING` 控制)。 +- 该阶段不依赖节点字典,避免循环依赖。 + +### 2.1 输入格式 + +`stage1_task_understanding(user_prompt: str)` 仅接受原始用户指令字符串。 + +示例输入: + +```json +{ + "user_prompt": "无人机当前在地面,到广场查找绿色公交车,找到后拍照。" +} +``` + +### 2.2 输出格式(TaskUnderstanding) + +```json +{ + "scene_mode": "scene4", + "intent_type": "search_and_photo", + "requires_relative_target": false, + "entities": { + "raw_prompt": "无人机当前在地面,到广场查找绿色公交车,找到后拍照。" + }, + "risk_flags": [], + "constraints": {} +} +``` + +字段说明: + +- `scene_mode`: `simple | scene1 | scene4` +- `intent_type`: 当前规则推导出的任务意图标签 +- `requires_relative_target`: 是否检测到相对方位需求 +- `entities`: 当前为轻量占位(最小包含 `raw_prompt`) +- `risk_flags`: 风险标记列表(如 `needs_manual_confirmation`) +- `constraints`: 约束占位(当前为空对象) + +## 3. Stage2:上下文绑定(ContextBinding) + +功能: + +- 按场景动态决定检索范围(location/pattern/rules) +- 从多知识库并行检索并汇总 +- 相对目标提取(`relative_refs`) +- 预计算航点(`precomputed_waypoints`,MVP) +- 基于意图与风险推导 `required_actions` + +核心代码: + +- `backend_service/src/retrieval/retriever.py::UnifiedRetriever` +- `backend_service/src/retrieval/adapters/chroma_adapter.py` +- `backend_service/src/llm/tool_runtime.py` +- `backend_service/src/pipeline/stages.py::stage2_context_binding()` +- 数据契约:`backend_service/src/pipeline/contracts.py::ContextBinding` + +多知识库策略: + +- 主集合:`location_kb`、`pattern_kb`、`rules_kb` +- 兼容集合:`drone_docs` +- 若主集合无结果,会尝试从 `drone_docs` + `kb_type` 过滤回退查询 + +### 3.1 输入格式 + +该阶段接收: + +- `user_prompt: str` +- Stage1 输出 `TaskUnderstanding` + +示例输入: + +```json +{ + "user_prompt": "无人机当前在空中,去广场南边40米,持续监控5分钟,发现人就拍照。", + "understanding": { + "scene_mode": "scene4", + "intent_type": "patrol_or_monitor", + "requires_relative_target": false, + "risk_flags": [], + "entities": {"raw_prompt": "无人机当前在空中,去广场南边40米,持续监控5分钟,发现人就拍照。"}, + "constraints": {} + } +} +``` + +### 3.2 输出格式(ContextBinding) + +```json +{ + "location_context": "地点:广场,坐标(x=120,y=30,z=0)...", + "pattern_context": "示例:先到达命名地点,再监控,再条件触发拍照...", + "rules_context": "", + "citations": { + "location": ["..."], + "pattern": ["..."], + "rules": [] + }, + "resolved_refs": {}, + "precomputed_waypoints": [ + {"x": 160.0, "y": 30.0, "z": 10.0} + ], + "relative_refs": [], + "required_actions": [ + "Sequence", + "fly_to_waypoint", + "loiter", + "object_detect", + "object_detected", + "take_photos" + ] +} +``` + +字段说明: + +- `*_context`: 分知识域拼接后的文本上下文 +- `citations`: 每个知识域的原始命中文档片段 +- `precomputed_waypoints`: Stage2 静态可解析时的预计算坐标 +- `relative_refs`: 相对目标结构化描述 +- `required_actions`: 后续用于节点裁剪注入的动作白名单 + +## 4. Stage3:BT 生成(BTPlanning) + +功能: + +- 组装 prompt(骨架 + 节点裁剪 + 示例 + 规则 + 检索结果) +- 调用对应模型生成严格 JSON +- 解析模型响应(含 reasoning 提取) + +核心代码: + +- `backend_service/src/prompting/composer.py::PromptComposer` +- `backend_service/src/llm/gateway.py::generate_json()` +- `backend_service/src/llm/response_parser.py` +- `backend_service/src/pipeline/stages.py::stage3_bt_planning()` +- 数据契约:`backend_service/src/pipeline/contracts.py::BTDraft` + +关键行为: + +- simple 模式与复杂模式使用不同客户端/模型配置 +- Stage3 强制关闭 thinking,强制 `response_format=json_object` +- 节点定义采用裁剪注入(非全量注入) + +### 4.1 输入格式 + +该阶段接收: + +- `user_prompt: str` +- `TaskUnderstanding` +- `ContextBinding` + +核心输入(示例): + +```json +{ + "scene_mode": "scene4", + "intent_type": "search_and_photo", + "required_actions": ["Sequence", "fly_to_waypoint", "rotate_search", "object_detected", "take_photos"], + "risk_flags": [], + "context_blocks": { + "location": "地点:广场...", + "pattern": "示例:先到达再搜索...", + "rules": "" + } +} +``` + +### 4.2 输出格式(BTDraft) + +```json +{ + "system_prompt": "...(裁剪后的系统提示词)...", + "user_prompt": "原始指令 + 参考知识增强段", + "allowed_nodes": { + "actions": ["fly_to_waypoint", "rotate_search", "take_photos"], + "conditions": ["object_detected"] + }, + "llm_raw_json": { + "root": { + "type": "Sequence", + "name": "Sequence", + "children": [ + {"type": "action", "name": "fly_to_waypoint", "params": {"x": 120, "y": 30, "z": 10, "acceptance_radius": 2}}, + {"type": "action", "name": "rotate_search", "params": {"target_class": "bus"}}, + {"type": "condition", "name": "object_detected", "params": {"target_class": "bus"}}, + {"type": "action", "name": "take_photos", "params": {"target_class": "bus", "track_time": 8}} + ] + } + }, + "reasoning_text": null, + "final_prompt": "=== System Prompt === ... === User Prompt === ..." +} +``` + +字段说明: + +- `system_prompt/user_prompt`: 实际发给模型的提示词 +- `allowed_nodes`: 节点裁剪结果(用于调试与复盘) +- `llm_raw_json`: 模型返回并解析后的原始计划 JSON +- `reasoning_text`: 可选推理文本(若模型返回) +- `final_prompt`: 完整组合记录(便于离线排查) + +## 5. Stage4:校验与后处理(ValidateAndPostprocess) + +功能: + +- JSON Schema 校验(simple / complex) +- 注入 `plan_id`、`visualization_url`、`final_prompt` +- 保存推理链与历史记录 +- 在复杂场景下注入 `context.relative_refs`(及可选 `context.resolved_refs`) + +核心代码: + +- `backend_service/src/validation/validator.py` +- `backend_service/src/validation/schema_provider.py` +- `backend_service/src/pipeline/stages.py::stage4_validate_and_postprocess()` +- `backend_service/src/py_tree_generator.py::render_visualization()` +- `backend_service/src/py_tree_generator.py::_save_history()` + +### 5.1 输入格式 + +该阶段接收: + +- `user_prompt: str` +- `TaskUnderstanding` +- `ContextBinding` +- `BTDraft` + +其中主载荷来自 `BTDraft.llm_raw_json`。 + +### 5.2 输出格式(最终 API 返回) + +复杂模式示例: + +```json +{ + "root": { + "type": "Sequence", + "name": "Sequence", + "children": [ + {"type": "action", "name": "fly_to_waypoint", "params": {"x": 120, "y": 30, "z": 10, "acceptance_radius": 2}}, + {"type": "action", "name": "rotate_search", "params": {"target_class": "bus"}}, + {"type": "condition", "name": "object_detected", "params": {"target_class": "bus"}}, + {"type": "action", "name": "take_photos", "params": {"target_class": "bus", "track_time": 8}} + ] + }, + "context": { + "relative_refs": [ + {"anchor": "front_building", "relation": "left", "distance_m": 20.0} + ], + "resolved_refs": { + "strategy": "backend_static_resolution", + "waypoints": [{"x": 120.0, "y": 30.0, "z": 10.0}] + } + }, + "plan_id": "6a924d0d-f1a7-4ef1-a9fa-31f73f3115ce", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt === ... === User Prompt === ..." +} +``` + +simple 模式示例: + +```json +{ + "root": { + "type": "action", + "name": "move_direction", + "params": {"direction": "north", "distance": 50} + }, + "plan_id": "0f0e5e5f-b9a2-4e6f-95f5-c95e9e6280a5", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt === ... === User Prompt === ..." +} +``` + +字段说明: + +- `root`: 通过 schema 校验后的行为树根节点 +- `context`: 仅在复杂模式且命中相对目标时追加 +- `plan_id`: 每次生成唯一 ID +- `visualization_url`: 最新可视化图访问路径 +- `final_prompt`: 生成时使用的完整提示词记录 + +## 6. 数据入库(RAG Ingestion)逻辑 + +入库脚本: + +- `tools/rag/ingest.py` + +行为: + +- 扫描 `tools/rag/knowledge_base/` +- 根据子目录推断 `kb_type`(location/pattern/rules) +- 同时写入: + - `drone_docs`(兼容) + - `location_kb` / `pattern_kb` / `rules_kb`(新检索路径) + +## 7. 可自定义修改点(推荐按优先级) + +### 7.1 场景与意图逻辑 + +可改文件: + +- `backend_service/src/pipeline/stages.py` + +可改内容: + +- `_infer_intent_type()`:扩展意图类别 +- `_extract_risk_flags()`:新增风险规则 +- `_derive_required_actions()`:调整规则推导的动作集合 + +### 7.2 Prompt 策略 + +可改文件: + +- `backend_service/src/prompting/composer.py` +- `backend_service/src/prompts/prompt_manifest.yaml` +- `backend_service/src/prompts/partials/*` + +可改内容: + +- 骨架片段选择 +- 节点裁剪规则(当前上限 30) +- 示例注入策略(何时注入 extra examples) +- 用户侧检索增强格式 + +### 7.3 模型路由与推理参数 + +可改文件: + +- `backend_service/src/llm/gateway.py` + +可改内容: + +- 分类模型与生成模型分流策略 +- `temperature`、`max_tokens`、重试次数 +- thinking 开关策略(Stage1/Stage3) + +### 7.4 检索策略 + +可改文件: + +- `backend_service/src/retrieval/retriever.py` +- `backend_service/src/retrieval/adapters/chroma_adapter.py` +- `tools/rag/ingest.py` + +可改内容: + +- 检索并发策略与 `top_k` +- 回退策略(主集合与兼容集合) +- kb_type 划分方式 +- 文档切分与 metadata 设计 + +### 7.5 相对目标解析 + +可改文件: + +- `backend_service/src/pipeline/stages.py` +- `backend_service/src/llm/tool_runtime.py` + +可改内容: + +- `relative_refs` 抽取规则 +- 静态解析能力(何时生成 `resolved_refs`) +- 与 UAV 端协议字段兼容策略 + +### 7.6 校验与输出协议 + +可改文件: + +- `backend_service/src/validation/validator.py` +- `backend_service/src/validation/schema_provider.py` +- `backend_service/src/py_tree_generator.py`(schema 来源) + +可改内容: + +- simple/complex schema 约束强度 +- 顶层 `context` 字段的可选校验 +- 失败错误信息与恢复策略 + +## 8. 关键环境变量 + +- `ORIN_IP` +- `OPENAI_API_KEY` +- `CLASSIFIER_MODEL` / `SIMPLE_MODEL` / `COMPLEX_MODEL` +- `CLASSIFIER_BASE_URL` / `SIMPLE_BASE_URL` / `COMPLEX_BASE_URL` +- `STAGE1_ENABLE_THINKING` +- `ENABLE_REASONING_CAPTURE` +- `REASONING_PREVIEW_LINES` + +## 9. 自定义改造建议(实践顺序) + +1. 先改 Stage1 规则推导(低风险,收益快) +2. 再改 PromptComposer 的裁剪与注入(控制长度与稳定性) +3. 再改检索策略(top_k、回退、metadata) +4. 最后改 schema 与输出协议(需联动执行端) + +## 10. 变更后最小回归清单 + +每次改造后至少验证: + +1. simple 指令:返回 `root.action`,且无 children +2. scene4 指令:有复合树结构,JSON 可解析 +3. relative 指令:复杂模式下出现 `context.relative_refs` +4. `/generate_plan` 不变更接口字段(兼容外部调用) +5. `python tools/rag/ingest.py` 可完成入库(或输出可定位错误) + diff --git a/README.md b/README.md index 1ddf2c6d..d149e023 100644 --- a/README.md +++ b/README.md @@ -1,454 +1,153 @@ -# 无人机自然语言控制项目 +# 无人机自然语言控制项目 (DronePlanning) -本项目构建了一个完整的无人机自然语言控制系统,集成了检索增强生成(RAG)知识库、大型语言模型(LLM)、FastAPI后端服务和ROS2通信,最终实现通过自然语言指令控制无人机执行复杂任务。 +本项目提供一个基于 `FastAPI + LLM + RAG` 的无人机任务规划后端。输入自然语言指令,输出严格 JSON 的行为树计划(`py_tree`)。 -## 项目结构 +> 当前版本已与 ROS2 解耦。 +> 推荐直接使用 `start_all.sh` / `start_all_vllm.sh` 启动。 -项目被清晰地划分为几个核心模块: +## 1. 当前代码结构 -``` -. -├── backend_service/ -│ ├── src/ # FastAPI应用核心代码 -│ │ ├── __init__.py -│ │ ├── main.py # 应用主入口,提供Web API -│ │ ├── py_tree_generator.py # RAG与LLM集成,生成py_tree -│ │ ├── prompts/ # LLM 提示词 -│ │ │ ├── system_prompt.txt # 复杂模式提示词(行为树与安全监控) -│ │ │ ├── simple_mode_prompt.txt # 简单模式提示词(单一原子动作JSON) -│ │ │ └── classifier_prompt.txt # 指令简单/复杂分类提示词 -│ │ ├── ... -│ ├── generated_visualizations/ # 存放最新生成的py_tree可视化图像 -│ ├── generated_reasoning_content/ # 存放最新推理链Markdown(.md) -│ └── requirements.txt # 后端服务的Python依赖 -│ -├── tools/ -│ ├── map/ # 【数据源】存放原始地图文件(如.world, .json) -│ ├── knowledge_base/ # 【处理后】存放build_knowledge_base.py生成的.ndjson文件 -│ ├── vector_store/ # 【数据库】存放最终的ChromaDB向量数据库 -│ ├── build_knowledge_base.py # 【步骤1】用于将原始数据转换为自然语言知识 -│ ├── ingest.py # 【步骤2】用于将自然语言知识摄入向量数据库 -│ └── test_llama_server.py # 直接调用本地8081端口llama-server,支持 --system / --system-file -│ -├── / # ROS2接口定义 (保持不变) -└── docs/ - └── README.md # 本说明文件 +```text +backend_service/src/ +├── main.py +├── py_tree_generator.py +├── models.py +├── websocket_manager.py +├── pipeline/ # 编排层(stage1~stage4) +├── llm/ # 模型网关/响应解析/工具运行时 +├── retrieval/ # 多知识库检索(Location/Pattern/Rules) +├── prompting/ # prompt manifest + 动态注入 +├── validation/ # schema provider + validator +├── prompts/ +└── tools/ ``` -## 核心配置:Orin IP 地址 +## 2. 环境准备(完整命令) -**重要提示:** 本项目的后端服务和知识库工具需要与在NVIDIA Jetson Orin设备上运行的服务进行通信(嵌入模型和LLM推理服务),**默认的IP地址为localhost**,所以使用电脑本地部署的模型服务同样可以,但是需要注意指定模型的端口。 - -在使用前,您必须配置正确的Orin设备IP地址。您可以通过以下两种方式之一进行设置: - -1. **设置环境变量 (推荐)**: - 在您的终端中设置一个名为 `ORIN_IP` 的环境变量。 - ```bash - export ORIN_IP="192.168.1.100" # 请替换为您的Orin设备的实际IP地址 - ``` - 脚本会优先使用这个环境变量。 - -2. **直接修改脚本**: - 如果您不想设置环境变量,可以打开 `tools/ingest.py` 和 `backend_service/src/py_tree_generator.py` 文件,找到 `orin_ip = os.getenv("ORIN_IP", "...")` 这样的行,并将默认的IP地址修改为您的Orin设备的实际IP地址。 - -**在继续后续步骤之前,请务必完成此项配置。** - -## 模型端口启动 - -本项目启动依赖于后端的模型推理服务,即`ORIN_IP`所指向的设备的模型服务端口,目前项目使用instruct模型与embedding模型实现流程,分别部署在8081端口与8090端口。 - -1. **推理模型部署**: - - 在`/llama.cpp/build/bin`路径下执行以下命令启动模型 - ```bash - ./llama-server -m ~/models/gguf/Qwen/Qwen3-8B-GGUF/Qwen3-8B-Q4_K_M.gguf --port 8081 --gpu-layers 36 --host 0.0.0.0 -c 8192 - ``` - -2. **Embedding模型部署** - - 在`/llama.cpp/build/bin`路径下执行以下命令启动模型 - ```bash - ./llama-server -m ~/models/gguf/Qwen/Qwen3-embedding-4B/Qwen3-Embedding-4B-Q4_K_M.gguf --gpu-layers 36 --port 8090 --embeddings --pooling last --host 0.0.0.0 - ``` - ---- - -## 指令分类与分流 - -后端在生成任务前会先对用户指令进行“简单/复杂”分类,并分流到不同提示词与模型: - -- 分类提示词:`backend_service/src/prompts/classifier_prompt.txt` -- 简单模式提示词:`backend_service/src/prompts/simple_mode_prompt.txt` -- 复杂模式提示词:`backend_service/src/prompts/system_prompt.txt` - -分类仅输出如下JSON之一:`{"mode":"simple"}` 或 `{"mode":"complex"}`。两种模式都会执行检索增强(RAG),将参考知识拼接到用户指令后再进行推理。 - -当为简单模式时,LLM仅输出: -`{"mode":"simple","action":{"name":"","params":{...}}}`。 -后端不会再自动封装为复杂行为树;将直接返回简单JSON,并附加 `plan_id` 与 `visualization_url`(单动作可视化)。 - -### 环境变量(可选) - -支持为“分类/简单/复杂”三类调用分别配置模型与Base URL(未设置时回退到默认本地配置): - -- `CLASSIFIER_MODEL`, `CLASSIFIER_BASE_URL` -- `SIMPLE_MODEL`, `SIMPLE_BASE_URL` -- `COMPLEX_MODEL`, `COMPLEX_BASE_URL` - -通用API Key:`OPENAI_API_KEY` - -推理链捕获相关: -- `ENABLE_REASONING_CAPTURE`:是否允许模型返回含有 的原文以便捕获推理链;默认 true。 -- `REASONING_PREVIEW_LINES`:在后端日志中打印推理链预览的行数;默认 20。 - -示例: -```bash -export CLASSIFIER_MODEL="qwen2.5-1.8b-instruct" -export SIMPLE_MODEL="qwen2.5-1.8b-instruct" -export COMPLEX_MODEL="qwen2.5-7b-instruct" -export CLASSIFIER_BASE_URL="http://$ORIN_IP:8081/v1" -export SIMPLE_BASE_URL="http://$ORIN_IP:8081/v1" -export COMPLEX_BASE_URL="http://$ORIN_IP:8081/v1" -export OPENAI_API_KEY="sk-no-key-required" - -# 推理链捕获(可选) -export ENABLE_REASONING_CAPTURE=true # 默认已为true;如需关闭,设置为 false -export REASONING_PREVIEW_LINES=30 # 调整日志预览行数 -``` - -### 测试简单模式 - -启动服务后,运行内置测试脚本: +在项目根目录执行: ```bash -cd tools -python test_api.py +cd /home/huangfukk/DronePlanning + +# 1) 创建并激活 venv +python3 -m venv backend_service/venv +source backend_service/venv/bin/activate + +# 2) 安装依赖 +pip install -r backend_service/requirements.txt + +# 3) 可选:设置设备地址(本地默认 localhost) +export ORIN_IP="localhost" ``` -示例输入:“简单模式,起飞” 或 “起飞到10米”。返回结果为简单JSON(无 `root`):包含 `mode`、`action`、`plan_id`、`visualization_url`。 +## 3. 启动服务(推荐) -### 直接调用 llama-server(绕过后端) - -当仅需测试本地 8081 端口的推理服务(OpenAI 兼容接口)时,可使用内置脚本: +### 3.1 llama.cpp 路线 ```bash +cd /home/huangfukk/DronePlanning +./start_all.sh start +``` + +常用命令: + +```bash +./start_all.sh stop +./start_all.sh restart +./start_all.sh vl +./start_all.sh restart-vl +./start_all.sh status +``` + +### 3.2 vLLM 路线 + +```bash +cd /home/huangfukk/DronePlanning +./start_all_vllm.sh start +``` + +常用命令: + +```bash +./start_all_vllm.sh stop +./start_all_vllm.sh restart +./start_all_vllm.sh status +``` + +## 4. 构建与入库(RAG) + +```bash +cd /home/huangfukk/DronePlanning +source backend_service/venv/bin/activate + +# 1) 从 map 生成知识文本(可选,已有知识可跳过) +python tools/rag/build_knowledge_base.py + +# 2) 入库到 ChromaDB(Location/Pattern/Rules + 兼容集合) +python tools/rag/ingest.py +``` + +> 运行 `ingest.py` 前,请确保 embedding 服务(8090)已启动。 + +## 5. 快速接口验证 + +```bash +cd /home/huangfukk/DronePlanning + +# 健康检查 +curl -s http://localhost:8000/docs >/dev/null && echo "fastapi ok" +curl -s http://localhost:8081/v1/models && echo "llm ok" + +# 生成计划 +curl -s http://localhost:8000/generate_plan \ + -H "Content-Type: application/json" \ + -d '{"user_prompt":"无人机当前在地面,到广场查找绿色公交车,找到就拍照"}' +``` + +## 6. 测试命令 + +```bash +cd /home/huangfukk/DronePlanning +source backend_service/venv/bin/activate + +# API 回归 +python tools/test_api.py + +# 直接测试 8081 的 OpenAI 兼容推理接口 python tools/test_llama_server.py \ - --system-file backend_service/src/prompts/system_prompt.txt \ + --system-file backend_service/src/prompts/system_prompt_vllm.txt \ --user "起飞到10米然后降落" \ --base-url "http://127.0.0.1:8081/v1" \ --verbose + +# 交互式/批量验证 +python tools/test_validate/run_tests.py ``` -说明: -- 支持 `--system` 或 `--system-file` 自定义提示词文件;`--system-file` 优先。 -- 默认解析 OpenAI 风格返回,若包含 `` 推理内容会显示在输出中(具体取决于模型和服务配置)。 +## 7. 关键 API ---- +- `POST /generate_plan` +- `POST /execute_mission`(当前返回 execution_disabled) +- `WS /ws/status` +- `GET /static/py_tree.png` -## 工作流程 - -整个系统的工作流程分为两个主要阶段: - -1. **知识库构建(一次性设置)**: 将环境信息、无人机能力等知识加工并存入向量数据库。 -2. **后端服务运行与交互**: 启动主服务,通过API接收指令、生成并执行任务。 - -### 阶段一:环境设置与编译 - -此阶段为项目准备好运行环境,仅需在初次配置或依赖变更时执行。一个稳定、隔离且兼容的环境是所有后续步骤成功的基础。 - -#### 1. 创建Conda环境 (关键步骤) - -为了从根源上避免本地Python环境与系统ROS 2环境的库版本冲突(特别是Python版本和C++标准库),我们**必须**使用Conda创建一个干净、隔离且版本精确的虚拟环境。 +## 8. 日志与排障 ```bash -# 1. 创建一个使用Python 3.10的新环境。 -# --name backend: 指定环境名称。 -# python=3.10: 指定Python版本,必须与ROS 2 Humble要求的版本一致。 -# --channel conda-forge: 使用conda-forge社区源,其包通常有更好的兼容性。 -# --no-default-packages: 关键!不安装Conda默认的包(如libgcc),避免与系统ROS 2的C++库冲突。 -conda create --name backend --channel conda-forge --no-default-packages python=3.10 - -# 2. 激活新创建的环境 -conda activate backend -``` - -#### 2. 安装所有Python依赖 - -在激活`backend`环境后,使用`pip`一次性安装所有依赖。`requirements.txt`已包含**运行时**(如fastapi, rclpy)和**编译时**(如empy, catkin-pkg, lark)所需的所有库。 - -```bash -# 确保在项目根目录 (drone/) 下执行 -pip install -r backend_service/requirements.txt -``` - -#### 3. 编译ROS 2接口 - -为了让后端服务能够像导入普通Python包一样导入我们自定义的Action接口 (`drone_interfaces`),你需要先使用`colcon`对其进行编译。 - -```bash -# 确保在项目根目录 (drone/) 下执行 -colcon build -``` -成功后,您会看到`build/`, `install/`, `log/`三个新目录。这一步会将`.action`文件转换为Python和C++代码。 - ---- - -### 阶段二:数据处理流水线 - -此阶段为RAG系统准备数据,让LLM能够理解任务环境。 - -#### 1. 准备原始数据 - -将你的原始数据文件(例如,`.world`, `.json` 文件等)放入 `tools/map/` 目录中。 - -#### 2. 数据预处理 - -运行脚本将原始数据“翻译”成自然语言知识。 - -```bash -# 确保在项目根目录 (drone/) 下,并已激活backend环境 -cd tools -python build_knowledge_base.py -``` -该脚本会扫描 `tools/map/` 目录,并在 `tools/knowledge_base/` 目录下生成对应的 `_knowledge.ndjson` 文件。 - -#### 3. 数据入库(Ingestion) - -运行脚本将处理好的知识加载到向量数据库中。 - -```bash -# 仍在tools/目录下执行 -python ingest.py -``` -该脚本会自动扫描 `tools/knowledge_base/` 目录,并将数据存入 `tools/vector_store/` 目录中。 - ---- - -### 阶段三:服务启动与测试 - -完成前两个阶段后,即可启动并测试后端服务。 - -#### 1. 启动所有服务(推荐方式:一键启动脚本) - -我们提供了一个一键启动脚本 `start_all.sh`,可以自动启动所有必需的服务: - -```bash -# 1. 切换到项目根目录 -cd /path/to/your/drone - -# 2. 使用一键启动脚本(推荐) -./start_all.sh start - -# 或者直接运行(start是默认命令) -./start_all.sh -``` - -**脚本功能:** -- 自动启动推理模型服务(llama-server,端口8081) -- 自动启动Embedding模型服务(llama-server,端口8090) -- 自动启动FastAPI后端服务(端口8000) -- 自动检查端口占用、模型文件、环境配置等 -- 自动等待服务就绪 -- 统一管理日志文件(保存在 `logs/` 目录) - -**环境变量配置(可选):** - -在运行脚本前,可以通过环境变量自定义配置: - -```bash -# 设置llama-server路径(如果不在默认位置) -export LLAMA_SERVER_DIR="/path/to/llama.cpp/build/bin" - -# 设置模型路径(如果不在默认位置) -export INFERENCE_MODEL="~/models/gguf/Qwen/Qwen3-8B-GGUF/Qwen3-8B-Q4_K_M.gguf" -export EMBEDDING_MODEL="~/models/gguf/Qwen/Qwen3-embedding-4B/Qwen3-Embedding-4B-Q4_K_M.gguf" - -# 设置Conda环境名称(如果使用不同的环境名) -export CONDA_ENV="backend" - -# 然后运行脚本 -./start_all.sh -``` - -**脚本命令:** - -```bash -./start_all.sh start # 启动所有服务(默认) -./start_all.sh stop # 停止所有服务 -./start_all.sh restart # 重启所有服务 -./start_all.sh status # 查看服务状态 -``` - -**日志查看:** - -所有服务的日志都保存在 `logs/` 目录下: -```bash -# 查看所有日志 +cd /home/huangfukk/DronePlanning tail -f logs/*.log - -# 查看特定服务日志 -tail -f logs/inference_model.log # 推理模型 -tail -f logs/embedding_model.log # Embedding模型 -tail -f logs/fastapi.log # FastAPI服务 ``` -#### 2. 手动启动服务(备选方式) +常见日志: -如果您需要手动控制每个服务的启动,可以按照以下步骤操作: +- `logs/inference_model.log`(llama.cpp 推理) +- `logs/vllm_inference_model.log`(vLLM 推理) +- `logs/embedding_model.log` +- `logs/fastapi.log` -**启动推理模型服务:** +如果 `tools/rag/ingest.py` 在 Chroma 初始化时报错,优先确认: -```bash -cd /llama.cpp/build/bin -./llama-server -m ~/models/gguf/Qwen/Qwen3-8B-GGUF/Qwen3-8B-Q4_K_M.gguf --port 8081 --gpu-layers 36 --host 0.0.0.0 -c 8192 -``` - -**启动Embedding模型服务:** - -在另一个终端中: -```bash -cd /llama.cpp/build/bin -./llama-server -m ~/models/gguf/Qwen/Qwen3-embedding-4B/Qwen3-Embedding-4B-Q4_K_M.gguf --gpu-layers 36 --port 8090 --embeddings --pooling last --host 0.0.0.0 -``` - -**启动FastAPI后端服务:** - -在第三个终端中: -```bash -# 1. 切换到项目根目录 -cd /path/to/your/drone - -# 2. 激活ROS 2编译环境 -# 作用:将我们编译好的`drone_interfaces`包的路径告知系统,否则Python会报`ModuleNotFoundError`。 -# 注意:此命令必须在每次打开新终端时执行一次。 -source install/setup.bash - -# 3. 激活Conda Python环境 -conda activate backend - -# 4. 启动FastAPI服务 -cd backend_service/ -uvicorn src.main:app --host 0.0.0.0 --port 8000 -``` - -当您看到日志中出现 `Uvicorn running on http://0.0.0.0:8000` 时,表示服务已成功启动。 - -#### 2. 运行API接口测试 - -我们提供了一个脚本来验证核心的“任务生成”功能。 - -**打开一个新的终端**,并执行以下命令: - -```bash -# 1. 切换到项目根目录 -cd /path/to/your/drone - -# 2. 激活Conda环境 -conda activate backend - -# 3. 运行测试脚本 -cd tools/ -python test_api.py -``` -如果一切正常,您将在终端看到一系列 `PASS` 信息,以及从服务器返回的Pytree JSON。 - -#### 3. API接口使用说明 - ---- - -## 故障排除 / 常见问题 (FAQ) - -以下是在配置和运行此项目时可能遇到的一些常见问题及其解决方案。 - -#### **Q1: 启动服务时报错 `ModuleNotFoundError: No module named 'drone_interfaces'`** - -- **原因**: 您当前的终端环境没有加载ROS 2工作空间的路径。仅仅激活Conda环境是不够的。 -- **解决方案**: 严格遵循“启动后端服务”章节的说明,在激活Conda环境**之前**,必须先运行 `source install/setup.bash` 命令。 - -#### **Q2: `colcon build` 编译失败,提示 `ModuleNotFoundError: No module named 'em'`, `'catkin_pkg'`, 或 `'lark'`** - -- **原因**: 您的Python环境中缺少ROS 2编译代码时所必需的依赖包。 -- **解决方案**: 我们已将所有已知的编译时依赖(`empy`, `catkin-pkg`, `lark`等)添加到了`requirements.txt`中。请确保您已激活正确的Conda环境,然后运行 `pip install -r backend_service/requirements.txt` 来安装它们。 - -#### **Q3: 启动服务时报错 `ImportError: ... GLIBCXX_... not found` 或 `ModuleNotFoundError: No module named 'rclpy._rclpy_pybind11'`** - -- **原因**: 您的Conda环境与系统ROS 2环境存在核心库冲突。最常见的原因是Python版本不匹配(例如,Conda是Python 3.11而ROS 2 Humble需要3.10),或者Conda自带的C++库与系统库冲突。 -- **解决方案**: 这是最棘手的环境问题。最可靠的解决方法是彻底删除当前的Conda环境 (`conda env remove --name backend`),然后严格按照本文档「环境设置」章节的说明,用正确的命令 (`conda create --name backend --channel conda-forge --no-default-packages python=3.10`) 重建一个干净、兼容的环境。 - -#### **Q4: 服务启动时,日志显示正在从网络上下载模型(例如 `all-MiniLM-L6-v2`)** - -- **原因**: 后端服务在连接向量数据库时,没有正确指定使用远程嵌入模型,导致ChromaDB退回到默认的、需要下载模型的本地嵌入函数。 -- **解决方案**: 此问题在当前代码中**已被修复**。`backend_service/src/py_tree_generator.py`现在会正确地将远程嵌入函数实例传递给ChromaDB。如果您在自己的代码中遇到此问题,请检查您的`get_collection`调用。 - -#### **Q5: 服务启动时,日志停在 `waiting for action server...`,无法访问API** - -- **原因**: 代码中存在阻塞式的`wait_for_server()`调用,它会一直等待直到无人机端的Action服务器上线,从而卡住了Web服务的启动流程。 -- **解决方案**: 此问题在当前代码中**已被修复**。`backend_service/src/ros2_client.py`现在使用非阻塞的方式初始化,并在发送任务时检查服务器是否可用。 - -##### **A. 生成任务计划** - -接收自然语言指令,返回生成的行为树(py_tree)JSON。 - -- **Endpoint**: `POST /generate_plan` -- **Request Body**: - ```json - { - "user_prompt": "无人机起飞到10米,然后前往机库,最后降落。" - } - ``` -- **Success Response(复杂模式)**: - ```json - { - "root": { ... }, - "plan_id": "some-unique-id", - "visualization_url": "/static/py_tree.png" - } - ``` -- **Success Response(简单模式)**: - ```json - { - "mode": "simple", - "action": { "name": "takeoff", "params": { "altitude": 10.0 } }, - "plan_id": "some-unique-id", - "visualization_url": "/static/py_tree.png" - } - ``` - -##### **B. 查看任务可视化** - -获取最新生成的行为树的可视化图像。 - -- **Endpoint**: `GET /static/py_tree.png` -- **Usage**: 在浏览器中直接打开 `http://<服务器IP>:8000/static/py_tree.png` 即可查看。每次成功调用 `/generate_plan` 后,该图像都会被更新。 - -##### **C. 执行任务** - -接收一个py_tree JSON,下发给无人机执行(当前为模拟执行)。 - -- **Endpoint**: `POST /execute_mission` -- **Request Body**: (使用 `/generate_plan` 返回的 `root` 对象) - ```json - { - "py_tree": { - "root": { ... } - } - } - ``` -- **Response**: - ```json - { - "status": "execution_started" - } - ``` - -##### **D. 接收实时状态** - -通过WebSocket连接,实时接收无人机在执行任务时的状态反馈。 - -- **Endpoint**: `WS /ws/status` -- **Usage**: 使用任意WebSocket客户端连接到 `ws://<服务器IP>:8000/ws/status`。当任务执行时,服务器会主动推送JSON消息,例如: - ```json - {"node_id": "takeoff_node_1", "status": 0} // 0: RUNNING - {"node_id": "takeoff_node_1", "status": 1} // 1: SUCCESS - ``` +1. 是否使用项目 venv 运行 +2. `tools/rag/vector_store/` 是否损坏(可备份后重建) +3. embedding 服务是否可访问(`http://localhost:8090/v1/embeddings`) \ No newline at end of file diff --git a/backend_service/generated_visualizations/py_tree.png b/backend_service/generated_visualizations/py_tree.png index 952c018f..98f8a0b3 100644 Binary files a/backend_service/generated_visualizations/py_tree.png and b/backend_service/generated_visualizations/py_tree.png differ diff --git a/backend_service/history/20260120_102200_plan.json b/backend_service/history/20260120_102200_plan.json new file mode 100644 index 00000000..bb7e1aaf --- /dev/null +++ b/backend_service/history/20260120_102200_plan.json @@ -0,0 +1,75 @@ +{ + "timestamp": "2026-01-20T10:22:00.135550", + "request": "无人机当前在地面,到广场查找穿黄色衣服的男子,找到后近距离拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchAndPhotoTask", + "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": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子", + "stop_distance": 5.0, + "speed": 1.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子", + "track_time": 10.0, + "min_confidence": 0.7, + "safe_distance": 10.0 + } + } + ] + }, + "plan_id": "be8b7d3b-8f16-4269-bdde-87c75b1ad7a2", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到广场查找穿黄色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102205_plan.json b/backend_service/history/20260120_102205_plan.json new file mode 100644 index 00000000..9b2309ce --- /dev/null +++ b/backend_service/history/20260120_102205_plan.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2026-01-20T10:22:05.030346", + "request": "无人机当前在空中,回到广场,对戴帽子的人进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "ReturnAndPhotoTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0, + "min_confidence": 0.7, + "safe_distance": 10.0 + } + } + ] + }, + "plan_id": "a7de7475-4607-4f32-850a-c7e7a233e91d", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到广场,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n在地图上有一个名为 'A地' 的地点或区域,它的building是'commercial',其中心位置坐标大约在东北天坐标系 (x:10, y:-10, z:2)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102211_plan.json b/backend_service/history/20260120_102211_plan.json new file mode 100644 index 00000000..046c742b --- /dev/null +++ b/backend_service/history/20260120_102211_plan.json @@ -0,0 +1,63 @@ +{ + "timestamp": "2026-01-20T10:22:11.292561", + "request": "无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchAndPhotoTask", + "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": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "绿色公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "绿色公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "绿色公交车", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "3198081f-c113-490a-8280-48c1398700d6", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102215_plan.json b/backend_service/history/20260120_102215_plan.json new file mode 100644 index 00000000..c3503b55 --- /dev/null +++ b/backend_service/history/20260120_102215_plan.json @@ -0,0 +1,44 @@ +{ + "timestamp": "2026-01-20T10:22:15.048953", + "request": "无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。", + "response": { + "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": "小汽车" + } + } + ] + }, + "plan_id": "0ad9c5e3-061f-4093-8ef6-30590fab08be", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102222_plan.json b/backend_service/history/20260120_102222_plan.json new file mode 100644 index 00000000..3afe1fc8 --- /dev/null +++ b/backend_service/history/20260120_102222_plan.json @@ -0,0 +1,75 @@ +{ + "timestamp": "2026-01-20T10:22:22.778164", + "request": "无人机当前在地面,到广场边上的施工区域内,发现有没带安全帽的飞近后拍照。", + "response": { + "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": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "没带安全帽" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "没带安全帽" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "没带安全帽", + "stop_distance": 2.0, + "speed": 1.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "没带安全帽", + "track_time": 10.0, + "min_confidence": 0.7, + "safe_distance": 10.0 + } + } + ] + }, + "plan_id": "7c79c1d7-da75-443b-bf8d-c4793aca3347", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到广场边上的施工区域内,发现有没带安全帽的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102226_plan.json b/backend_service/history/20260120_102226_plan.json new file mode 100644 index 00000000..9c0e15c5 --- /dev/null +++ b/backend_service/history/20260120_102226_plan.json @@ -0,0 +1,47 @@ +{ + "timestamp": "2026-01-20T10:22:26.947445", + "request": "无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。", + "response": { + "root": { + "type": "Sequence", + "name": "EmergencyReturnAndLand", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "traffic_light", + "description": "红绿灯" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "traffic_light", + "description": "红绿灯" + } + }, + { + "type": "action", + "name": "land", + "params": { + "mode": "home" + } + } + ] + }, + "plan_id": "3dacecff-fcd3-4794-9fc4-aa79f12bfc93", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102232_plan.json b/backend_service/history/20260120_102232_plan.json new file mode 100644 index 00000000..b7a5c4a7 --- /dev/null +++ b/backend_service/history/20260120_102232_plan.json @@ -0,0 +1,64 @@ +{ + "timestamp": "2026-01-20T10:22:32.895784", + "request": "无人机当前在地面,快速去往东边100米,有身穿黑色衣服,头戴帽子的男子在挟持他人,对其进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "system_checks", + "params": { + "check_level": "basic" + } + }, + { + "type": "action", + "name": "takeoff", + "params": { + "altitude": 10.0 + } + }, + { + "type": "action", + "name": "move_direction", + "params": { + "direction": "east", + "distance": 100.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "身穿黑色衣服,头戴帽子的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "身穿黑色衣服,头戴帽子的男子" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "身穿黑色衣服,头戴帽子的男子", + "track_time": 10.0, + "min_confidence": 0.7, + "safe_distance": 10.0 + } + } + ] + }, + "plan_id": "abfad3e3-52a9-4559-b071-9b03b25ecbf8", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,快速去往东边100米,有身穿黑色衣服,头戴帽子的男子在挟持他人,对其进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102236_plan.json b/backend_service/history/20260120_102236_plan.json new file mode 100644 index 00000000..b80f80a3 --- /dev/null +++ b/backend_service/history/20260120_102236_plan.json @@ -0,0 +1,40 @@ +{ + "timestamp": "2026-01-20T10:22:36.657828", + "request": "无人机当前在空中,离黑色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "黑色衣服戴帽子的人", + "stop_distance": 3.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "黑色衣服戴帽子的人", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "拍摄完成后返航" + } + } + ] + }, + "plan_id": "250b268d-4008-4a1c-a069-f81d8cb5460f", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,离黑色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102742_plan.json b/backend_service/history/20260120_102742_plan.json new file mode 100644 index 00000000..ea0e667f --- /dev/null +++ b/backend_service/history/20260120_102742_plan.json @@ -0,0 +1,75 @@ +{ + "timestamp": "2026-01-20T10:27:42.950414", + "request": "无人机当前在地面,到广场查找穿黄色衣服的男子,找到后近距离拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchAndPhotoTask", + "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": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子", + "stop_distance": 5.0, + "speed": 1.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子", + "track_time": 10.0, + "min_confidence": 0.7, + "safe_distance": 10.0 + } + } + ] + }, + "plan_id": "cb39b40a-23d4-4137-aed2-f46fa48ef559", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到广场查找穿黄色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102747_plan.json b/backend_service/history/20260120_102747_plan.json new file mode 100644 index 00000000..6d5eece7 --- /dev/null +++ b/backend_service/history/20260120_102747_plan.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2026-01-20T10:27:47.810068", + "request": "无人机当前在空中,回到广场,对戴帽子的人进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "ReturnAndPhotoTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0, + "min_confidence": 0.7, + "safe_distance": 10.0 + } + } + ] + }, + "plan_id": "314fd7a3-ce06-43b3-b41e-ad2799e721b6", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到广场,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n在地图上有一个名为 'A地' 的地点或区域,它的building是'commercial',其中心位置坐标大约在东北天坐标系 (x:10, y:-10, z:2)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102755_plan.json b/backend_service/history/20260120_102755_plan.json new file mode 100644 index 00000000..afe7a3a9 --- /dev/null +++ b/backend_service/history/20260120_102755_plan.json @@ -0,0 +1,63 @@ +{ + "timestamp": "2026-01-20T10:27:55.448053", + "request": "无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchAndPhotoTask", + "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": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "绿色公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "绿色公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "绿色公交车", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "ee61a13a-322c-470e-ac7e-0e5e196bbb97", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_102759_plan.json b/backend_service/history/20260120_102759_plan.json new file mode 100644 index 00000000..2cf6a3ea --- /dev/null +++ b/backend_service/history/20260120_102759_plan.json @@ -0,0 +1,44 @@ +{ + "timestamp": "2026-01-20T10:27:59.180807", + "request": "无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。", + "response": { + "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": "小汽车" + } + } + ] + }, + "plan_id": "9fa49966-4fe5-4d32-acd9-2a843aa9a14c", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_111034_plan.json b/backend_service/history/20260120_111034_plan.json new file mode 100644 index 00000000..acb42a96 --- /dev/null +++ b/backend_service/history/20260120_111034_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T11:10:34.427639", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "ae58fc13-fe94-4341-96b5-9e5834e286d5", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_111135_plan.json b/backend_service/history/20260120_111135_plan.json new file mode 100644 index 00000000..6738022e --- /dev/null +++ b/backend_service/history/20260120_111135_plan.json @@ -0,0 +1,82 @@ +{ + "timestamp": "2026-01-20T11:11:35.274415", + "request": "无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 300 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "CheckAndPhoto", + "children": [ + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "person", + "description": "发现人" + } + }, + { + "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": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "e0a85798-4655-4611-a6b3-2fe43da1ffe4", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_111754_plan.json b/backend_service/history/20260120_111754_plan.json new file mode 100644 index 00000000..27bc9600 --- /dev/null +++ b/backend_service/history/20260120_111754_plan.json @@ -0,0 +1,82 @@ +{ + "timestamp": "2026-01-20T11:17:54.654820", + "request": "无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 300 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "CheckAndPhoto", + "children": [ + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "person", + "description": "发现人" + } + }, + { + "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": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "853d356f-c80a-40fa-b18d-dbb0ddf5d257", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_111830_plan.json b/backend_service/history/20260120_111830_plan.json new file mode 100644 index 00000000..f6087d7d --- /dev/null +++ b/backend_service/history/20260120_111830_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-01-20T11:18:30.884529", + "request": "无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "监控与拍照", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "Parallel", + "name": "并行监控5分钟", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 300 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "check_and_photo", + "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": "返航" + } + } + ] + }, + "plan_id": "6a6b3695-b927-4ab3-969e-c0d3f2875999", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_111903_plan.json b/backend_service/history/20260120_111903_plan.json new file mode 100644 index 00000000..31e3a834 --- /dev/null +++ b/backend_service/history/20260120_111903_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-01-20T11:19:03.006321", + "request": "无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "监控与拍照", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "Parallel", + "name": "并行监控5分钟", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 300 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "check_and_photo", + "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": "返航" + } + } + ] + }, + "plan_id": "4771b0c0-7a95-434b-8698-aab243eb218a", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115045_plan.json b/backend_service/history/20260120_115045_plan.json new file mode 100644 index 00000000..2ba994d6 --- /dev/null +++ b/backend_service/history/20260120_115045_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-01-20T11:50:45.656715", + "request": "无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "监控与拍照", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "Parallel", + "name": "并行监控5分钟", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 300 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "check_and_photo", + "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": "返航" + } + } + ] + }, + "plan_id": "d2a4dfda-a722-4bc7-b657-3920873a64ce", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115356_plan.json b/backend_service/history/20260120_115356_plan.json new file mode 100644 index 00000000..5c563768 --- /dev/null +++ b/backend_service/history/20260120_115356_plan.json @@ -0,0 +1,82 @@ +{ + "timestamp": "2026-01-20T11:53:56.740325", + "request": "无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndSearch", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 300 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "CheckAndPhoto", + "children": [ + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "person", + "description": "发现人" + } + }, + { + "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": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "5bc7d90f-1d3b-480d-af55-cc0cfd52d7ca", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115415_plan.json b/backend_service/history/20260120_115415_plan.json new file mode 100644 index 00000000..ac586005 --- /dev/null +++ b/backend_service/history/20260120_115415_plan.json @@ -0,0 +1,82 @@ +{ + "timestamp": "2026-01-20T11:54:15.178751", + "request": "无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 300 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "CheckAndPhoto", + "children": [ + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "person", + "description": "发现人" + } + }, + { + "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": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "34e71da5-a10d-485f-b58f-2155815847a8", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115816_plan.json b/backend_service/history/20260120_115816_plan.json new file mode 100644 index 00000000..9345212e --- /dev/null +++ b/backend_service/history/20260120_115816_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-01-20T11:58:16.067601", + "request": "无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -217.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 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": "返航" + } + } + ] + }, + "plan_id": "44db1903-84e2-4fd3-904e-8e12a33cd06d", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115851_plan.json b/backend_service/history/20260120_115851_plan.json new file mode 100644 index 00000000..4c7fdcbc --- /dev/null +++ b/backend_service/history/20260120_115851_plan.json @@ -0,0 +1,77 @@ +{ + "timestamp": "2026-01-20T11:58:51.555057", + "request": "无人机当前在地面,到广场查找穿黄色衣服的男子,找到后近距离拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchAndPhotoTask", + "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": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子", + "count": 1 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子", + "stop_distance": 5.0 + } + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿黄色衣服的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "ec7665ec-c1d2-4c2c-91a3-edbc6f39d5a0", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到广场查找穿黄色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115900_plan.json b/backend_service/history/20260120_115900_plan.json new file mode 100644 index 00000000..5a082661 --- /dev/null +++ b/backend_service/history/20260120_115900_plan.json @@ -0,0 +1,50 @@ +{ + "timestamp": "2026-01-20T11:59:00.646114", + "request": "无人机当前在空中,回到广场,对戴帽子的人进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "ReturnToSquareAndPhoto", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "count": 1 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "f6229e39-43ad-448a-bd0d-efc47ce62ccc", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到广场,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n在地图上有一个名为 'A地' 的地点或区域,它的building是'commercial',其中心位置坐标大约在东北天坐标系 (x:10, y:-10, z:2)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115910_plan.json b/backend_service/history/20260120_115910_plan.json new file mode 100644 index 00000000..5626eebc --- /dev/null +++ b/backend_service/history/20260120_115910_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T11:59:10.668232", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "6df926ed-085f-4d79-8eb3-12ec7d7f1333", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115921_plan.json b/backend_service/history/20260120_115921_plan.json new file mode 100644 index 00000000..e7dceee3 --- /dev/null +++ b/backend_service/history/20260120_115921_plan.json @@ -0,0 +1,64 @@ +{ + "timestamp": "2026-01-20T11:59:21.652098", + "request": "无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。", + "response": { + "root": { + "type": "Sequence", + "name": "MainSearchTask", + "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": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "绿色公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "绿色公交车", + "count": 1 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "绿色公交车", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "e99dae89-e667-4da4-bac0-d647bf3efe33", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115929_plan.json b/backend_service/history/20260120_115929_plan.json new file mode 100644 index 00000000..61e4a772 --- /dev/null +++ b/backend_service/history/20260120_115929_plan.json @@ -0,0 +1,45 @@ +{ + "timestamp": "2026-01-20T11:59:29.678848", + "request": "无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。", + "response": { + "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": "小汽车", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "8555fc21-0367-40a7-9cc6-35da4801e1e0", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115942_plan.json b/backend_service/history/20260120_115942_plan.json new file mode 100644 index 00000000..d35e2382 --- /dev/null +++ b/backend_service/history/20260120_115942_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-01-20T11:59:42.146243", + "request": "无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "监控与拍照", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "Parallel", + "name": "并行监控5分钟", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 300 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "check_and_photo", + "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": "返航" + } + } + ] + }, + "plan_id": "61bc6dd3-312f-4cbc-b69f-353228cabee2", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_115954_plan.json b/backend_service/history/20260120_115954_plan.json new file mode 100644 index 00000000..e3c55328 --- /dev/null +++ b/backend_service/history/20260120_115954_plan.json @@ -0,0 +1,72 @@ +{ + "timestamp": "2026-01-20T11:59:54.402129", + "request": "无人机当前在地面,到广场边上的施工区域内,发现有没带安全帽的飞近后拍照。", + "response": { + "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": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "没带安全帽的" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "没带安全帽的" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "stop_distance": 5.0, + "speed": 2.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "没带安全帽的", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "3c2a2e19-dac1-4921-8a8a-5c296ee98ddc", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到广场边上的施工区域内,发现有没带安全帽的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_120002_plan.json b/backend_service/history/20260120_120002_plan.json new file mode 100644 index 00000000..8ba8b54c --- /dev/null +++ b/backend_service/history/20260120_120002_plan.json @@ -0,0 +1,45 @@ +{ + "timestamp": "2026-01-20T12:00:02.418253", + "request": "无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "traffic_light" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "traffic_light" + } + }, + { + "type": "action", + "name": "land", + "params": { + "mode": "current" + } + } + ] + }, + "plan_id": "9d172338-b790-4561-9307-073cafb20cd0", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_120014_plan.json b/backend_service/history/20260120_120014_plan.json new file mode 100644 index 00000000..6c4ebae0 --- /dev/null +++ b/backend_service/history/20260120_120014_plan.json @@ -0,0 +1,62 @@ +{ + "timestamp": "2026-01-20T12:00:14.295434", + "request": "无人机当前在地面,快速去往东边100米,有身穿黑色衣服,头戴帽子的男子在挟持他人,对其进行拍照。", + "response": { + "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": "move_direction", + "params": { + "direction": "east", + "distance": 100.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "身穿黑色衣服,头戴帽子的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "身穿黑色衣服,头戴帽子的男子" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "身穿黑色衣服,头戴帽子的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "e20b80dc-4182-44aa-85a0-71af617c1350", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,快速去往东边100米,有身穿黑色衣服,头戴帽子的男子在挟持他人,对其进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_120022_plan.json b/backend_service/history/20260120_120022_plan.json new file mode 100644 index 00000000..679c63c3 --- /dev/null +++ b/backend_service/history/20260120_120022_plan.json @@ -0,0 +1,40 @@ +{ + "timestamp": "2026-01-20T12:00:22.177082", + "request": "无人机当前在空中,离黑色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "黑色衣服戴帽子的人", + "stop_distance": 3.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "黑色衣服戴帽子的人", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "8dab708d-6cdd-4472-81ca-d6b6bc074b92", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,离黑色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_120356_plan.json b/backend_service/history/20260120_120356_plan.json new file mode 100644 index 00000000..b1d49575 --- /dev/null +++ b/backend_service/history/20260120_120356_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:03:56.851980", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "f0c6cc6b-3fdc-4c73-9238-d0f29977c78c", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_120657_plan.json b/backend_service/history/20260120_120657_plan.json new file mode 100644 index 00000000..35b534ef --- /dev/null +++ b/backend_service/history/20260120_120657_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:06:57.926070", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "6e4a8d0e-3796-45f8-a6c4-600f3a5f40b2", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_122451_plan.json b/backend_service/history/20260120_122451_plan.json new file mode 100644 index 00000000..0e9455cf --- /dev/null +++ b/backend_service/history/20260120_122451_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:24:51.385808", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "b3a37739-d072-439b-8f08-bfffff0d3b57", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_122519_plan.json b/backend_service/history/20260120_122519_plan.json new file mode 100644 index 00000000..dfa16e89 --- /dev/null +++ b/backend_service/history/20260120_122519_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:25:19.634702", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "66daee80-c2ac-45c1-b615-9981c58297b5", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_122538_plan.json b/backend_service/history/20260120_122538_plan.json new file mode 100644 index 00000000..8d24aa61 --- /dev/null +++ b/backend_service/history/20260120_122538_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:25:38.887364", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "e61ed3ee-9891-4d11-9b5c-dbd551ebf9ee", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123118_plan.json b/backend_service/history/20260120_123118_plan.json new file mode 100644 index 00000000..da858c45 --- /dev/null +++ b/backend_service/history/20260120_123118_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:31:18.078676", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "e95eb3f0-c87e-490c-b00c-85a46dc99cc8", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123142_plan.json b/backend_service/history/20260120_123142_plan.json new file mode 100644 index 00000000..670c4d20 --- /dev/null +++ b/backend_service/history/20260120_123142_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:31:42.735921", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "877649ca-cc04-4424-91e4-64bf05ce5dcc", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123322_plan.json b/backend_service/history/20260120_123322_plan.json new file mode 100644 index 00000000..c656f175 --- /dev/null +++ b/backend_service/history/20260120_123322_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:33:22.982495", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "eada3e09-b748-466e-a378-f19ae347722b", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123342_plan.json b/backend_service/history/20260120_123342_plan.json new file mode 100644 index 00000000..d507b99d --- /dev/null +++ b/backend_service/history/20260120_123342_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:33:42.593589", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "6bfbadcc-8a25-4fd3-bfd2-a7f6f8bee166", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123516_plan.json b/backend_service/history/20260120_123516_plan.json new file mode 100644 index 00000000..adbaa414 --- /dev/null +++ b/backend_service/history/20260120_123516_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:35:16.673933", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "7daa46e3-9d00-440d-82ad-0313b313869d", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123521_plan.json b/backend_service/history/20260120_123521_plan.json new file mode 100644 index 00000000..fdd3f7cc --- /dev/null +++ b/backend_service/history/20260120_123521_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:35:21.646715", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "cd1a6e32-b339-41de-bc52-c57a5f5c9b82", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123526_plan.json b/backend_service/history/20260120_123526_plan.json new file mode 100644 index 00000000..1bd58177 --- /dev/null +++ b/backend_service/history/20260120_123526_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:35:26.703544", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "fb27d754-e5a8-444d-825b-9c66e4f5a010", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123531_plan.json b/backend_service/history/20260120_123531_plan.json new file mode 100644 index 00000000..1a3d915b --- /dev/null +++ b/backend_service/history/20260120_123531_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:35:31.729013", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "4cecd5b2-17ac-4b07-b7dd-b2c99716d836", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123536_plan.json b/backend_service/history/20260120_123536_plan.json new file mode 100644 index 00000000..f77f3dd2 --- /dev/null +++ b/backend_service/history/20260120_123536_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:35:36.697159", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "639bcb3f-8c92-44e8-8669-ae90cda430e4", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123541_plan.json b/backend_service/history/20260120_123541_plan.json new file mode 100644 index 00000000..02a8a89d --- /dev/null +++ b/backend_service/history/20260120_123541_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:35:41.660637", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "91b3c270-dfa3-4f53-9a01-baa902ae756a", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123546_plan.json b/backend_service/history/20260120_123546_plan.json new file mode 100644 index 00000000..e9c72a89 --- /dev/null +++ b/backend_service/history/20260120_123546_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:35:46.586938", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "67f190b3-78dc-4ef6-a9f1-837a3c302f79", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123551_plan.json b/backend_service/history/20260120_123551_plan.json new file mode 100644 index 00000000..489e36f5 --- /dev/null +++ b/backend_service/history/20260120_123551_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:35:51.521647", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "199dcd71-4865-4901-b049-6c36c8cd40ac", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123556_plan.json b/backend_service/history/20260120_123556_plan.json new file mode 100644 index 00000000..e27f6ef0 --- /dev/null +++ b/backend_service/history/20260120_123556_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:35:56.414032", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "ddb38cec-1962-4661-b06d-e6b5d169e264", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123601_plan.json b/backend_service/history/20260120_123601_plan.json new file mode 100644 index 00000000..02f0935c --- /dev/null +++ b/backend_service/history/20260120_123601_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:36:01.426433", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "83124562-1323-4584-990c-f12cce0187ab", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_123801_plan.json b/backend_service/history/20260120_123801_plan.json new file mode 100644 index 00000000..aec5658c --- /dev/null +++ b/backend_service/history/20260120_123801_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:38:01.470372", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "aa7215df-e4ff-4444-8187-1cb0e4e49e42", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东南天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_125258_plan.json b/backend_service/history/20260120_125258_plan.json new file mode 100644 index 00000000..3206edc1 --- /dev/null +++ b/backend_service/history/20260120_125258_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:52:58.623905", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "25b2b536-70fb-47be-b651-75a37a8edf5a", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东南天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260120_125315_plan.json b/backend_service/history/20260120_125315_plan.json new file mode 100644 index 00000000..9a0448bb --- /dev/null +++ b/backend_service/history/20260120_125315_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-01-20T12:53:15.813610", + "request": "无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 160.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "过往的公交车" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "过往的公交车", + "track_time": 10.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "返航" + } + } + ] + }, + "plan_id": "2db48247-04a2-4e38-a3bf-c77509f0c429", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // --- 任务1执行---\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"目标描述\"}},\n // ----条件判断(根据指令替换) -----\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n // --- 任务2执行---\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}}, \n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点\n3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“紧急返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 九、工具调用输出规则(必须遵守)\n当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东南天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260121_202255_plan.json b/backend_service/history/20260121_202255_plan.json new file mode 100644 index 00000000..cddf8399 --- /dev/null +++ b/backend_service/history/20260121_202255_plan.json @@ -0,0 +1,63 @@ +{ + "timestamp": "2026-01-21T20:22:55.678397", + "request": "无人机当前在地面,到广场边上的施工区域内,发现有没带安全帽的飞近后拍照。", + "response": { + "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": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "没带安全帽" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "没带安全帽" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "没带安全帽", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "da99aab1-4b78-452c-ae6d-cace25747c06", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\",\"desc\":\"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right\",\"distance\":\"[1,10000],缺省则持续移动\",\"speed\":\"float,可选\",\"desc\":\"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}}\n ],\n \"control_flow\": [\n {\"name\":\"Sequence\",\"params\":{},\"children\":\"子节点数组(按序执行,全成功则成功)\"},\n {\"name\":\"Selector\",\"params\":{\"memory\":\"默认true\"},\"children\":\"子节点数组(执行到成功为止)\"},\n {\"name\":\"Parallel\",\"params\":{\"policy\":\"all_success/success_on_one\"},\"children\":\"子节点数组(同时执行,默认all_success)\"}\n ],\n \"decorators\": [\n {\"name\":\"SuccessIsFailure\",\"params\":{},\"child\":\"单一子节点(将子节点的成功结果反转为失败)\"}\n ]\n}\n```\n\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"name\": \"主任务-监控与拍照\",\n \"type\": \"Sequence\",\n \"children\": [\n {\n \"name\": \"fly_to_waypoint\",\n \"type\": \"action\",\n \"params\": {\"x\": 50.0, \"y\": 50.0, \"z\": 10.0}\n },\n {\n \"name\": \"并行监控5分钟\",\n \"type\": \"Parallel\",\n \"params\": { \"policy\": \"success_on_one\" },\n \"children\": [\n {\n \"name\": \"loiter\",\n \"type\": \"action\",\n \"params\": { \"duration\": 300 }\n },\n {\n \"name\": \"SuccessIsFailure\",\n \"type\": \"decorator\",\n \"child\": {\n \"name\": \"check_and_photo\",\n \"type\": \"Sequence\",\n \"children\": [\n {\"name\": \"object_detect\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"object_detected\", \"type\": \"condition\", \"params\": { \"target_class\": \"person\" }},\n {\"name\": \"take_photos\", \"type\": \"action\", \"params\": { \"target_class\": \"person\" }}\n ]\n }\n }\n ]\n },\n {\n \"name\": \"return_emergency\",\n \"type\": \"action\",\n \"params\": {\"reason\": \"返航\"} // 返航回起飞点\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移(如“广场西边200米”)时,**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n- `base`: 参考地点的ENU坐标(含x/y/z)\n- `direction`: east/west/north/south/up/down\n- `distance`: 偏移距离(米)\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到广场边上的施工区域内,发现有没带安全帽的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222653_plan.json b/backend_service/history/20260203_222653_plan.json new file mode 100644 index 00000000..69249622 --- /dev/null +++ b/backend_service/history/20260203_222653_plan.json @@ -0,0 +1,79 @@ +{ + "timestamp": "2026-02-03T22:26:53.171741", + "request": "无人机起飞到80米高度后,先移动至搜索区,搜索并锁定任一红色车辆,跟踪接近距离目标车辆10m后进行拍照,完成拍照后返回。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "system_checks", + "params": { + "check_level": "comprehensive" + } + }, + { + "type": "action", + "name": "takeoff", + "params": { + "altitude": 80.0 + } + }, + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -23.8, + "y": 292.8, + "z": 14.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "car", + "description": "红色车辆" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "car", + "description": "红色车辆" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "car", + "description": "红色车辆", + "stop_distance": 10.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "car", + "description": "红色车辆", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "99775191-aa61-4a30-842f-6fa315d5bfb6", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机起飞到80米高度后,先移动至搜索区,搜索并锁定任一红色车辆,跟踪接近距离目标车辆10m后进行拍照,完成拍照后返回。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222711_plan.json b/backend_service/history/20260203_222711_plan.json new file mode 100644 index 00000000..2aefc0f3 --- /dev/null +++ b/backend_service/history/20260203_222711_plan.json @@ -0,0 +1,72 @@ +{ + "timestamp": "2026-02-03T22:27:11.063507", + "request": "无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "MainSearchTask", + "children": [ + { + "type": "action", + "name": "system_checks", + "params": { + "check_level": "comprehensive" + } + }, + { + "type": "action", + "name": "takeoff", + "params": { + "altitude": 14.0 + } + }, + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -23.8, + "y": 292.8, + "z": 14.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "9110d71f-e2c2-420a-96f2-4d98568983b8", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东北天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222720_plan.json b/backend_service/history/20260203_222720_plan.json new file mode 100644 index 00000000..253d4e3e --- /dev/null +++ b/backend_service/history/20260203_222720_plan.json @@ -0,0 +1,53 @@ +{ + "timestamp": "2026-02-03T22:27:20.888326", + "request": "无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -17.0, + "y": 360.0, + "z": 3.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + } + ] + }, + "plan_id": "62a5868f-7637-491e-bec0-f3e16f908ac9", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n大楼外围四个点东北天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'广场'。属性:开阔区域。东北天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n在地图上有一个名为 '学生宿舍' 的地点或区域,它的building是'dormitory',其中心位置坐标大约在东北天坐标系 (x:5, y:3, z:2)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222813_plan.json b/backend_service/history/20260203_222813_plan.json new file mode 100644 index 00000000..e199616a --- /dev/null +++ b/backend_service/history/20260203_222813_plan.json @@ -0,0 +1,72 @@ +{ + "timestamp": "2026-02-03T22:28:13.964344", + "request": "无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "MainSearchTask", + "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": 5.0, + "y": 3.0, + "z": 2.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "009050fa-b2bd-4fd6-8cf9-a74e5883d10c", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'飞行场地'。属性:学生宿舍。东南天坐标系坐标:(x:5, y:3, z:2)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222823_plan.json b/backend_service/history/20260203_222823_plan.json new file mode 100644 index 00000000..809df37a --- /dev/null +++ b/backend_service/history/20260203_222823_plan.json @@ -0,0 +1,49 @@ +{ + "timestamp": "2026-02-03T22:28:23.360137", + "request": "无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 5.0, + "y": 3.0, + "z": 2.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "43dc460a-4e86-4ee0-af30-eb9edda589ac", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'飞行场地'。属性:学生宿舍。东南天坐标系坐标:(x:5, y:3, z:2)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'广场'。属性:开阔区域。东南天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222838_plan.json b/backend_service/history/20260203_222838_plan.json new file mode 100644 index 00000000..8740dce3 --- /dev/null +++ b/backend_service/history/20260203_222838_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-02-03T22:28:38.114413", + "request": "无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -45.0, + "y": 3.0, + "z": 2.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "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": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "744d6290-e0f9-4b54-a44e-41d3febaccaa", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n地点:'飞行场地'。属性:学生宿舍。东南天坐标系坐标:(x:5, y:3, z:2)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222840_plan.json b/backend_service/history/20260203_222840_plan.json new file mode 100644 index 00000000..0d9e9524 --- /dev/null +++ b/backend_service/history/20260203_222840_plan.json @@ -0,0 +1,18 @@ +{ + "timestamp": "2026-02-03T22:28:40.559081", + "request": "无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。", + "response": { + "root": { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "person", + "description": "长头发", + "count": 1 + } + }, + "plan_id": "b25d5e7c-5a21-4b59-8db4-51b06d9b0ece", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'飞行场地'。属性:学生宿舍。东南天坐标系坐标:(x:5, y:3, z:2)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222851_plan.json b/backend_service/history/20260203_222851_plan.json new file mode 100644 index 00000000..b11554fd --- /dev/null +++ b/backend_service/history/20260203_222851_plan.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2026-02-03T22:28:51.265404", + "request": "无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchPhotoConfirmReturn", + "children": [ + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "确认后返航" + } + } + ] + }, + "plan_id": "cf15f590-8071-4e85-8085-a44631c4df53", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'飞行场地'。属性:学生宿舍。东南天坐标系坐标:(x:5, y:3, z:2)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222907_plan.json b/backend_service/history/20260203_222907_plan.json new file mode 100644 index 00000000..4864e9d1 --- /dev/null +++ b/backend_service/history/20260203_222907_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-02-03T22:29:07.591116", + "request": "无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -45.0, + "y": 3.0, + "z": 2.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 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": "任务完成" + } + } + ] + }, + "plan_id": "5361bb98-9e43-4113-9d5d-ed5cd9280dae", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'飞行场地'。属性:学生宿舍。东南天坐标系坐标:(x:5, y:3, z:2)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222910_plan.json b/backend_service/history/20260203_222910_plan.json new file mode 100644 index 00000000..46c74c99 --- /dev/null +++ b/backend_service/history/20260203_222910_plan.json @@ -0,0 +1,19 @@ +{ + "timestamp": "2026-02-03T22:29:10.461393", + "request": "无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。", + "response": { + "root": { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 5.0, + "y": 3.0, + "z": 2.0, + "acceptance_radius": 2.0 + } + }, + "plan_id": "917ad9da-87e5-45aa-8866-7391cc3d9bb6", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n地点:'飞行场地'。属性:学生宿舍。东南天坐标系坐标:(x:5, y:3, z:2)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222943_plan.json b/backend_service/history/20260203_222943_plan.json new file mode 100644 index 00000000..744619df --- /dev/null +++ b/backend_service/history/20260203_222943_plan.json @@ -0,0 +1,62 @@ +{ + "timestamp": "2026-02-03T22:29:43.144164", + "request": "无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。", + "response": { + "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": "move_direction", + "params": { + "direction": "east", + "distance": 30.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "eea6fbea-5ddf-41b6-9e23-dc5af009bcd5", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n地点:'飞行场地'。属性:学生宿舍。东南天坐标系坐标:(x:5, y:3, z:2)。\n\n地点:'广场'。属性:开阔区域。东南天坐标系坐标:(x:-17, y:360.0, z:3.0)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_222954_plan.json b/backend_service/history/20260203_222954_plan.json new file mode 100644 index 00000000..ad3bf9cf --- /dev/null +++ b/backend_service/history/20260203_222954_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-02-03T22:29:54.873889", + "request": "无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的", + "stop_distance": 3.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "d6491c81-93cf-4118-a060-54b11f9a7a85", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\n\n对于查找搜索类任务,比如查找某个人或者东西,我们可以让无人机执行rotate_search动作节点,让无人机以自身为原点旋转,观察四周,确保无人机主动寻找目标。\n\n地点:'飞行场地'。属性:学生宿舍。东南天坐标系坐标:(x:5, y:3, z:2)。\n\n大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。\n\n地点:'研究所正大门'。别名:'大门'、'入口'。东南天坐标系坐标:(x:-23.8, y:292.8, z:14)。建议悬停高度:14米。适合任务:定点侦察、拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_223851_plan.json b/backend_service/history/20260203_223851_plan.json new file mode 100644 index 00000000..9e61c37e --- /dev/null +++ b/backend_service/history/20260203_223851_plan.json @@ -0,0 +1,72 @@ +{ + "timestamp": "2026-02-03T22:38:51.218905", + "request": "无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。", + "response": { + "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": 0.0, + "y": 0.0, + "z": 10.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "d68270f9-62e7-4072-ba26-f76f116222f2", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_223901_plan.json b/backend_service/history/20260203_223901_plan.json new file mode 100644 index 00000000..de4a9e88 --- /dev/null +++ b/backend_service/history/20260203_223901_plan.json @@ -0,0 +1,49 @@ +{ + "timestamp": "2026-02-03T22:39:01.772800", + "request": "无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "399ce10f-9686-49ad-b6df-daa6717240ec", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_223912_plan.json b/backend_service/history/20260203_223912_plan.json new file mode 100644 index 00000000..c9f4c788 --- /dev/null +++ b/backend_service/history/20260203_223912_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-02-03T22:39:12.584723", + "request": "无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "b8a79268-161d-4f05-bf42-cc919228b297", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_223915_plan.json b/backend_service/history/20260203_223915_plan.json new file mode 100644 index 00000000..da8e2700 --- /dev/null +++ b/backend_service/history/20260203_223915_plan.json @@ -0,0 +1,20 @@ +{ + "timestamp": "2026-02-03T22:39:15.445134", + "request": "无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。", + "response": { + "root": { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "长头发", + "track_time": 10.0, + "min_confidence": 0.7, + "safe_distance": 10.0 + } + }, + "plan_id": "4135b436-8426-4d8e-9dc5-c1bce6c2ad8f", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_223925_plan.json b/backend_service/history/20260203_223925_plan.json new file mode 100644 index 00000000..64e613ed --- /dev/null +++ b/backend_service/history/20260203_223925_plan.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2026-02-03T22:39:25.722794", + "request": "无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchPhotoConfirmReturn", + "children": [ + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "确认后返航" + } + } + ] + }, + "plan_id": "c8a0f69e-3048-4698-afdc-79d7578e59a2", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_223938_plan.json b/backend_service/history/20260203_223938_plan.json new file mode 100644 index 00000000..244f3ba5 --- /dev/null +++ b/backend_service/history/20260203_223938_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-02-03T22:39:38.664122", + "request": "无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 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": "任务完成" + } + } + ] + }, + "plan_id": "eda15250-6372-4e09-830f-de1a3d39ba69", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_223941_plan.json b/backend_service/history/20260203_223941_plan.json new file mode 100644 index 00000000..a897e098 --- /dev/null +++ b/backend_service/history/20260203_223941_plan.json @@ -0,0 +1,19 @@ +{ + "timestamp": "2026-02-03T22:39:41.389938", + "request": "无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。", + "response": { + "root": { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0, + "acceptance_radius": 2.0 + } + }, + "plan_id": "c0345338-e196-4ff3-921c-051dbd1292b7", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224016_plan.json b/backend_service/history/20260203_224016_plan.json new file mode 100644 index 00000000..32cd700d --- /dev/null +++ b/backend_service/history/20260203_224016_plan.json @@ -0,0 +1,67 @@ +{ + "timestamp": "2026-02-03T22:40:16.200096", + "request": "无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。", + "response": { + "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": "move_direction", + "params": { + "direction": "east", + "distance": 30.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "1d6a52d8-ab9a-49f1-addd-c8aa467c9a6b", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224025_plan.json b/backend_service/history/20260203_224025_plan.json new file mode 100644 index 00000000..d26b42b0 --- /dev/null +++ b/backend_service/history/20260203_224025_plan.json @@ -0,0 +1,40 @@ +{ + "timestamp": "2026-02-03T22:40:25.394878", + "request": "无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "stop_distance": 3.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "b670c79a-a78c-4917-a2f9-65b6f5c54b25", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224508_plan.json b/backend_service/history/20260203_224508_plan.json new file mode 100644 index 00000000..5b799456 --- /dev/null +++ b/backend_service/history/20260203_224508_plan.json @@ -0,0 +1,72 @@ +{ + "timestamp": "2026-02-03T22:45:08.194714", + "request": "无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。", + "response": { + "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": 0.0, + "y": 0.0, + "z": 10.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "f63e2a97-ee52-4d3f-a7ad-ee10fd54a851", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224518_plan.json b/backend_service/history/20260203_224518_plan.json new file mode 100644 index 00000000..7bdddcf1 --- /dev/null +++ b/backend_service/history/20260203_224518_plan.json @@ -0,0 +1,49 @@ +{ + "timestamp": "2026-02-03T22:45:18.737509", + "request": "无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "0053bd1e-43af-4773-9428-f1bedaf67154", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224529_plan.json b/backend_service/history/20260203_224529_plan.json new file mode 100644 index 00000000..7f063f61 --- /dev/null +++ b/backend_service/history/20260203_224529_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-02-03T22:45:29.166949", + "request": "无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "f79a1aa2-9ce8-4b88-b420-5b2f3c7de763", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224532_plan.json b/backend_service/history/20260203_224532_plan.json new file mode 100644 index 00000000..d4c77d84 --- /dev/null +++ b/backend_service/history/20260203_224532_plan.json @@ -0,0 +1,20 @@ +{ + "timestamp": "2026-02-03T22:45:32.078412", + "request": "无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。", + "response": { + "root": { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "长头发", + "track_time": 10.0, + "min_confidence": 0.7, + "safe_distance": 10.0 + } + }, + "plan_id": "07a61f17-24e8-4b02-bab3-2096815f101b", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224542_plan.json b/backend_service/history/20260203_224542_plan.json new file mode 100644 index 00000000..76b02b1a --- /dev/null +++ b/backend_service/history/20260203_224542_plan.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2026-02-03T22:45:42.791926", + "request": "无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchPhotoConfirmReturn", + "children": [ + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "确认后返航" + } + } + ] + }, + "plan_id": "589f32e6-4d47-4fed-958e-1eec1056941a", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224557_plan.json b/backend_service/history/20260203_224557_plan.json new file mode 100644 index 00000000..2f92b345 --- /dev/null +++ b/backend_service/history/20260203_224557_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-02-03T22:45:57.135813", + "request": "无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 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": "任务完成" + } + } + ] + }, + "plan_id": "65a604a6-cdfa-44ba-88d7-9915f1169da4", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224559_plan.json b/backend_service/history/20260203_224559_plan.json new file mode 100644 index 00000000..245b8e91 --- /dev/null +++ b/backend_service/history/20260203_224559_plan.json @@ -0,0 +1,19 @@ +{ + "timestamp": "2026-02-03T22:45:59.912722", + "request": "无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。", + "response": { + "root": { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0, + "acceptance_radius": 2.0 + } + }, + "plan_id": "700ad50b-9526-459c-b3dd-9565262f2c82", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224642_plan.json b/backend_service/history/20260203_224642_plan.json new file mode 100644 index 00000000..14d10cdb --- /dev/null +++ b/backend_service/history/20260203_224642_plan.json @@ -0,0 +1,72 @@ +{ + "timestamp": "2026-02-03T22:46:42.703755", + "request": "无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。", + "response": { + "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": 0.0, + "y": 0.0, + "z": 10.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "ec05c1a4-720d-4cde-9590-a66f81a72561", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224653_plan.json b/backend_service/history/20260203_224653_plan.json new file mode 100644 index 00000000..d4dadd31 --- /dev/null +++ b/backend_service/history/20260203_224653_plan.json @@ -0,0 +1,49 @@ +{ + "timestamp": "2026-02-03T22:46:53.524196", + "request": "无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "ad362dde-0c33-435b-9870-9d29beee9278", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224705_plan.json b/backend_service/history/20260203_224705_plan.json new file mode 100644 index 00000000..211ad72a --- /dev/null +++ b/backend_service/history/20260203_224705_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-02-03T22:47:05.352347", + "request": "无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "3f212bfa-0915-4bc4-a9b1-59266248c05c", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224707_plan.json b/backend_service/history/20260203_224707_plan.json new file mode 100644 index 00000000..b1c1758b --- /dev/null +++ b/backend_service/history/20260203_224707_plan.json @@ -0,0 +1,18 @@ +{ + "timestamp": "2026-02-03T22:47:07.535141", + "request": "无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。", + "response": { + "root": { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "person", + "description": "长头发", + "count": 1 + } + }, + "plan_id": "70eb9449-6857-4a27-a99c-7c6708d12db1", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224718_plan.json b/backend_service/history/20260203_224718_plan.json new file mode 100644 index 00000000..e4a7ae18 --- /dev/null +++ b/backend_service/history/20260203_224718_plan.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2026-02-03T22:47:18.336790", + "request": "无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchPhotoConfirmReturn", + "children": [ + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "确认后返航" + } + } + ] + }, + "plan_id": "00394bf3-1df8-49aa-917a-4d81acb535ec", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224732_plan.json b/backend_service/history/20260203_224732_plan.json new file mode 100644 index 00000000..9697df68 --- /dev/null +++ b/backend_service/history/20260203_224732_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-02-03T22:47:32.805286", + "request": "无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 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": "任务完成" + } + } + ] + }, + "plan_id": "c7382dba-30c3-48b9-b175-ec42cb7f90d8", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224736_plan.json b/backend_service/history/20260203_224736_plan.json new file mode 100644 index 00000000..af22e402 --- /dev/null +++ b/backend_service/history/20260203_224736_plan.json @@ -0,0 +1,19 @@ +{ + "timestamp": "2026-02-03T22:47:36.602361", + "request": "无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。", + "response": { + "root": { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0, + "acceptance_radius": 2.0 + } + }, + "plan_id": "ee762817-34de-406a-a216-f0ec3e0edb6e", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224810_plan.json b/backend_service/history/20260203_224810_plan.json new file mode 100644 index 00000000..7f4ce22f --- /dev/null +++ b/backend_service/history/20260203_224810_plan.json @@ -0,0 +1,67 @@ +{ + "timestamp": "2026-02-03T22:48:10.609517", + "request": "无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。", + "response": { + "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": "move_direction", + "params": { + "direction": "east", + "distance": 30.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "24122475-d286-4034-b9a5-e7e37aff8564", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_224818_plan.json b/backend_service/history/20260203_224818_plan.json new file mode 100644 index 00000000..e8a118eb --- /dev/null +++ b/backend_service/history/20260203_224818_plan.json @@ -0,0 +1,40 @@ +{ + "timestamp": "2026-02-03T22:48:18.761567", + "request": "无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "stop_distance": 3.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "f31e2045-5987-4aab-8543-31d15fa17704", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_225639_plan.json b/backend_service/history/20260203_225639_plan.json new file mode 100644 index 00000000..ee5d3e0e --- /dev/null +++ b/backend_service/history/20260203_225639_plan.json @@ -0,0 +1,72 @@ +{ + "timestamp": "2026-02-03T22:56:39.254734", + "request": "无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。", + "response": { + "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": 0.0, + "y": 0.0, + "z": 10.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "e6dd50ed-5ce0-4032-91fa-bb7b9e28233c", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_225648_plan.json b/backend_service/history/20260203_225648_plan.json new file mode 100644 index 00000000..1bc5e8e8 --- /dev/null +++ b/backend_service/history/20260203_225648_plan.json @@ -0,0 +1,49 @@ +{ + "timestamp": "2026-02-03T22:56:48.750931", + "request": "无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "35f56771-b284-4d58-b738-e404f5aa29a1", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_225701_plan.json b/backend_service/history/20260203_225701_plan.json new file mode 100644 index 00000000..f89bec39 --- /dev/null +++ b/backend_service/history/20260203_225701_plan.json @@ -0,0 +1,56 @@ +{ + "timestamp": "2026-02-03T22:57:01.072769", + "request": "无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "ded7065b-453b-4b55-8737-1e00388f6a06", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_225712_plan.json b/backend_service/history/20260203_225712_plan.json new file mode 100644 index 00000000..04466535 --- /dev/null +++ b/backend_service/history/20260203_225712_plan.json @@ -0,0 +1,63 @@ +{ + "timestamp": "2026-02-03T22:57:12.412732", + "request": "无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。", + "response": { + "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": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "长头发的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "长头发的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "长头发的人", + "track_time": 5.0 + } + } + ] + }, + "plan_id": "02ac6a8d-630b-43d7-8374-679cc07f37ed", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_225723_plan.json b/backend_service/history/20260203_225723_plan.json new file mode 100644 index 00000000..b05e59b0 --- /dev/null +++ b/backend_service/history/20260203_225723_plan.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2026-02-03T22:57:23.020981", + "request": "无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。", + "response": { + "root": { + "type": "Sequence", + "name": "SearchPhotoConfirmReturn", + "children": [ + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "确认后返航" + } + } + ] + }, + "plan_id": "642ee3ce-45a3-4c7b-b1a4-b323cc0dbf6b", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_225737_plan.json b/backend_service/history/20260203_225737_plan.json new file mode 100644 index 00000000..4e9b23d7 --- /dev/null +++ b/backend_service/history/20260203_225737_plan.json @@ -0,0 +1,78 @@ +{ + "timestamp": "2026-02-03T22:57:37.553450", + "request": "无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 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": "任务完成" + } + } + ] + }, + "plan_id": "f54dd51c-b9f5-4a9a-802f-941bba44ca12", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_225750_plan.json b/backend_service/history/20260203_225750_plan.json new file mode 100644 index 00000000..4a8bfd6b --- /dev/null +++ b/backend_service/history/20260203_225750_plan.json @@ -0,0 +1,72 @@ +{ + "timestamp": "2026-02-03T22:57:50.218309", + "request": "无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。", + "response": { + "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": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "未带帽子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "未带帽子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "未带帽子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "未带帽子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "dca5568f-e062-4d82-b8db-61ef2bed669a", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_225823_plan.json b/backend_service/history/20260203_225823_plan.json new file mode 100644 index 00000000..b6ac5f65 --- /dev/null +++ b/backend_service/history/20260203_225823_plan.json @@ -0,0 +1,67 @@ +{ + "timestamp": "2026-02-03T22:58:23.868929", + "request": "无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。", + "response": { + "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": "move_direction", + "params": { + "direction": "east", + "distance": 30.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "046d1b14-1c37-4143-98c5-5a80a61213a1", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_225833_plan.json b/backend_service/history/20260203_225833_plan.json new file mode 100644 index 00000000..ec470b7b --- /dev/null +++ b/backend_service/history/20260203_225833_plan.json @@ -0,0 +1,40 @@ +{ + "timestamp": "2026-02-03T22:58:33.708571", + "request": "无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "stop_distance": 3.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "7e65d4ad-dd23-49ac-80d9-70b648c17cd4", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260203_230445_plan.json b/backend_service/history/20260203_230445_plan.json new file mode 100644 index 00000000..180aa789 --- /dev/null +++ b/backend_service/history/20260203_230445_plan.json @@ -0,0 +1,62 @@ +{ + "timestamp": "2026-02-03T23:04:45.105296", + "request": "起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航", + "response": { + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "takeoff", + "params": { + "altitude": 1.0 + } + }, + { + "type": "action", + "name": "move_direction", + "params": { + "direction": "forward", + "distance": 1.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿迷彩衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿迷彩衣服的人" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿迷彩衣服的人", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "fd63525f-41b0-46e4-8405-09278be536ac", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260220_222942_plan.json b/backend_service/history/20260220_222942_plan.json new file mode 100644 index 00000000..2fa1a62e --- /dev/null +++ b/backend_service/history/20260220_222942_plan.json @@ -0,0 +1,17 @@ +{ + "timestamp": "2026-02-20T22:29:42.389703", + "request": "无人机当前在空中,往北飞50米", + "response": { + "root": { + "type": "action", + "name": "move_direction", + "params": { + "direction": "north", + "distance": 50.0 + } + }, + "plan_id": "3982c111-a833-4c62-9d18-c68fe455d8cc", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n\n=== User Prompt ===\n无人机当前在空中,往北飞50米" + } +} \ No newline at end of file diff --git a/backend_service/history/20260220_223029_plan.json b/backend_service/history/20260220_223029_plan.json new file mode 100644 index 00000000..124fd727 --- /dev/null +++ b/backend_service/history/20260220_223029_plan.json @@ -0,0 +1,68 @@ +{ + "timestamp": "2026-02-20T22:30:29.161370", + "request": "无人机当前在地面,到广场查找穿红色衣服的人,找到后拍照", + "response": { + "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": 0.0, + "y": 0.0, + "z": 10.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "c9301ba8-87c1-450d-a9cf-b8f5f3702397", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(裁剪后)\n#### 1. 可用节点定义 (必须遵守)\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 任务意图标签\n- intent_type: `search_and_photo`\n\n=== User Prompt ===\n无人机当前在地面,到广场查找穿红色衣服的人,找到后拍照\n\n---\n参考知识:\n【任务模式】\n无人机已在空中,先调整高度后沿建筑外围巡查人员,发现目标后拍照。\n\n在命名区域发现目标后等待人工确认,再执行拍照或返航。\n\n前往命名地点后搜索目标并拍照。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260220_223230_plan.json b/backend_service/history/20260220_223230_plan.json new file mode 100644 index 00000000..61bb1180 --- /dev/null +++ b/backend_service/history/20260220_223230_plan.json @@ -0,0 +1,93 @@ +{ + "timestamp": "2026-02-20T22:32:30.764753", + "request": "无人机当前在地面,去面前大楼左边20米巡查并拍照", + "response": { + "root": { + "type": "Sequence", + "name": "FlyLeft20MAndPhotoPerson", + "children": [ + { + "type": "action", + "name": "system_checks", + "params": { + "check_level": "comprehensive" + } + }, + { + "type": "action", + "name": "takeoff", + "params": { + "altitude": 10.0 + } + }, + { + "type": "action", + "name": "move_direction", + "params": { + "direction": "west", + "distance": 20.0 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "DetectAndPhotoPerson", + "children": [ + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "person", + "description": "人员" + } + }, + { + "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": "land", + "params": { + "mode": "current" + } + } + ] + }, + "context": { + "relative_refs": [ + { + "anchor": "front_building", + "relation": "front", + "distance_m": 20.0 + }, + { + "anchor": "front_building", + "relation": "left", + "distance_m": 20.0 + } + ] + }, + "plan_id": "12b27a1d-d3de-4b1a-8298-a9a9b144b83e", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(裁剪后)\n#### 1. 可用节点定义 (必须遵守)\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例\n\n#### 场景 1:地面到12米绕外围查看打开的窗户(Sequence)\n**指令**:“无人机当前在地面,去面前大楼的12米高处,绕着外围看有没有打开的窗户,发现则进行拍照。”\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlyPerimeterAndPhotoWindows\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Parallel\",\n \"name\": \"PatrolAndDetectWindows\",\n \"params\": {\"policy\": \"success_on_one\"},\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": [\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 289.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 292.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0}\n ],\n \"coordinate_frame\": \"local_enu\"\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"DetectOpenWindowsWhileFlying\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"window\",\"description\":\"打开的窗户\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"window\",\"description\":\"打开的窗户\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"window\",\"description\":\"打开的窗户\",\"track_time\":10.0}}\n ]\n }\n }\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:地面到12米沿外围查找打开的窗户(Sequence)\n**指令**:“无人机当前在地面,去面前大楼的12米高处,沿着外围查找所有打开的窗户并拍照。”\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlyPerimeterAndPhotoOpenWindows\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Parallel\",\n \"name\": \"PatrolAndDetectOpenWindows\",\n \"params\": {\"policy\": \"success_on_one\"},\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": [\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 289.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 292.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0}\n ],\n \"coordinate_frame\": \"local_enu\"\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"DetectAllOpenWindowsWhileFlying\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"window\",\"description\":\"打开的窗户\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"window\",\"description\":\"打开的窗户\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"window\",\"description\":\"打开的窗户\",\"track_time\":10.0}}\n ]\n }\n }\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:空中上升后绕外围侦察打开窗户(Sequence)\n**指令**:“无人机当前在空中,再往上飞3米,接着绕这栋楼外围侦察有没有打开的窗户,看到了就拍照传回来。”\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"RiseAndDetectOpenWindows\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"move_direction\",\"params\":{\"direction\":\"up\",\"distance\":3.0}},\n {\n \"type\": \"Parallel\",\n \"name\": \"PatrolAndDetectOpenWindows\",\n \"params\": {\"policy\": \"success_on_one\"},\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": [\n {\"x\": -24.0, \"y\": 241.8},\n {\"x\": -108.5, \"y\": 241.8},\n {\"x\": -108.5, \"y\": 289.8},\n {\"x\": -24.0, \"y\": 292.8},\n {\"x\": -24.0, \"y\": 241.8}\n ],\n \"coordinate_frame\": \"local_enu\"\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"DetectOpenWindowsWhileFlying\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"window\",\"description\":\"打开的窗户\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"window\",\"description\":\"打开的窗户\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"window\",\"description\":\"打开的窗户\",\"track_time\":10.0}}\n ]\n }\n }\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 4:地面到12米绕外围巡视杂物堆积(Sequence)\n**指令**:“无人机当前在地面,去面前大楼的12米高处,绕着外围巡视杂物堆积现象,发现则进行拍照。”\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlyPerimeterAndPhotoGarbageObserve\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Parallel\",\n \"name\": \"PatrolAndDetectGarbageObserve\",\n \"params\": {\"policy\": \"success_on_one\"},\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": [\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 289.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 292.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0}\n ],\n \"coordinate_frame\": \"local_enu\"\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"DetectGarbageWhileFlying\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"garbage\",\"description\":\"杂物堆积\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"garbage\",\"description\":\"杂物堆积\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"garbage\",\"description\":\"杂物堆积\",\"track_time\":10.0}}\n ]\n }\n }\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 5:地面到12米沿外围查找杂物堆积(Sequence)\n**指令**:“无人机当前在地面,去面前大楼的12米高处,沿着外围查找所有的杂物堆积并拍照。”\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlyPerimeterAndPhotoGarbage\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Parallel\",\n \"name\": \"PatrolAndDetectGarbage\",\n \"params\": {\"policy\": \"success_on_one\"},\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": [\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 289.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 292.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0}\n ],\n \"coordinate_frame\": \"local_enu\"\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"DetectGarbageWhileFlying\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"garbage\",\"description\":\"杂物堆积\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"garbage\",\"description\":\"杂物堆积\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"garbage\",\"description\":\"杂物堆积\",\"track_time\":10.0}}\n ]\n }\n }\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 6:空中下降后绕外围侦察杂物堆积(Sequence)\n**指令**:“无人机当前在空中,往下飞3米,接着绕这栋楼外围侦察有没有杂物堆积,看到了就拍照传回来。”\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"DescendAndDetectGarbage\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"move_direction\",\"params\":{\"direction\":\"down\",\"distance\":3.0}},\n {\n \"type\": \"Parallel\",\n \"name\": \"PatrolAndDetectGarbage\",\n \"params\": {\"policy\": \"success_on_one\"},\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": [\n {\"x\": -24.0, \"y\": 241.8},\n {\"x\": -108.5, \"y\": 241.8},\n {\"x\": -108.5, \"y\": 289.8},\n {\"x\": -24.0, \"y\": 292.8},\n {\"x\": -24.0, \"y\": 241.8}\n ],\n \"coordinate_frame\": \"local_enu\"\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"DetectGarbageWhileFlying\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"garbage\",\"description\":\"杂物堆积\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"garbage\",\"description\":\"杂物堆积\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"garbage\",\"description\":\"杂物堆积\",\"track_time\":10.0}}\n ]\n }\n }\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 7:地面到12米绕外围查看是否有人(Sequence)\n**指令**:“无人机当前在地面,去面前大楼的12米高处,绕着外围看有没有人,发现则进行拍照。”\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlyPerimeterAndPhotoPerson\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Parallel\",\n \"name\": \"PatrolAndDetectPerson\",\n \"params\": {\"policy\": \"success_on_one\"},\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": [\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 289.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 292.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0}\n ],\n \"coordinate_frame\": \"local_enu\"\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"DetectPersonWhileFlying\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"人员\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"人员\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"人员\",\"track_time\":10.0}}\n ]\n }\n }\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 8:地面到12米沿外围逆时针查找人员(Sequence)\n**指令**:“无人机当前在地面,去面前大楼的12米高处,沿着外围逆时针查找所有的人并拍照。”\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlyPerimeterCounterClockwiseAndPhotoPerson\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Parallel\",\n \"name\": \"PatrolCCWAndDetectPerson\",\n \"params\": {\"policy\": \"success_on_one\"},\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": [\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 292.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 289.8, \"depth\": 12.0},\n {\"x\": -108.5, \"y\": 241.8, \"depth\": 12.0},\n {\"x\": -24.0, \"y\": 241.8, \"depth\": 12.0}\n ],\n \"coordinate_frame\": \"local_enu\"\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"DetectPersonWhileFlying\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"人员\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"人员\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"人员\",\"track_time\":10.0}}\n ]\n }\n }\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 9:空中上升后沿建筑外围侦察人员(Sequence)\n**指令**:“无人机当前在空中,再往上飞3米,接着绕这栋楼外围侦察有没有人,看到了就拍照传回来。”\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"RiseAndPatrolPerimeter\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"move_direction\",\"params\":{\"direction\":\"up\",\"distance\":3.0}},\n {\n \"type\": \"Parallel\",\n \"name\": \"PatrolAndDetect\",\n \"params\": {\"policy\": \"success_on_one\"},\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": [\n {\"x\": -24.0, \"y\": 241.8},\n {\"x\": -108.5, \"y\": 241.8},\n {\"x\": -108.5, \"y\": 289.8},\n {\"x\": -24.0, \"y\": 292.8},\n {\"x\": -24.0, \"y\": 241.8}\n ],\n \"coordinate_frame\": \"local_enu\"\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"DetectAndPhotoWhileFlying\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\",\"description\":\"建筑外围人员\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"建筑外围人员\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"建筑外围人员\",\"track_time\":10.0}}\n ]\n }\n }\n ]\n }\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 任务意图标签\n- intent_type: `search_and_photo`\n\n=== User Prompt ===\n无人机当前在地面,去面前大楼左边20米巡查并拍照\n\n---\n参考知识:\n【任务模式】\n无人机已在空中,先调整高度后沿建筑外围巡查人员,发现目标后拍照。\n\n先到达命名地点偏移区域执行监控,到时后返航。\n\n在命名区域发现目标后等待人工确认,再执行拍照或返航。\n\n【规则知识】\n有明确目的地时使用 fly_to_waypoint;无明确目的地的立即返航才使用 return_emergency。\n\n当只有方向+距离且无具体地点名词时,优先使用 move_direction;不要生成 fly_to_waypoint。\n\n仅当用户明确要求人工确认时才注入 manual_confirmation 节点;否则禁止主动添加。\n---" + } +} \ No newline at end of file diff --git a/backend_service/history/20260220_223648_plan.json b/backend_service/history/20260220_223648_plan.json new file mode 100644 index 00000000..646f649b --- /dev/null +++ b/backend_service/history/20260220_223648_plan.json @@ -0,0 +1,68 @@ +{ + "timestamp": "2026-02-20T22:36:48.248177", + "request": "无人机当前在地面,到广场查找绿色公交车,找到就拍照", + "response": { + "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": 0.0, + "y": 0.0, + "z": 10.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "bus", + "description": "绿色公交车" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "bus", + "description": "绿色公交车" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "bus", + "description": "绿色公交车", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "e61747f6-00e3-4bab-ba35-fc7ace6d65f5", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(裁剪后)\n#### 1. 可用节点定义 (必须遵守)\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n## 任务意图标签\n- intent_type: `search_and_photo`\n\n=== User Prompt ===\n无人机当前在地面,到广场查找绿色公交车,找到就拍照\n\n---\n参考知识:\n【任务模式】\n无人机已在空中,先调整高度后沿建筑外围巡查人员,发现目标后拍照。\n\n前往命名地点后搜索目标并拍照。\n\n在命名区域发现目标后等待人工确认,再执行拍照或返航。\n---" + } +} \ No newline at end of file diff --git a/backend_service/src/__pycache__/__init__.cpython-313.pyc b/backend_service/src/__pycache__/__init__.cpython-313.pyc index 4e403fce..3a3bc7d5 100644 Binary files a/backend_service/src/__pycache__/__init__.cpython-313.pyc and b/backend_service/src/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend_service/src/__pycache__/main.cpython-313.pyc b/backend_service/src/__pycache__/main.cpython-313.pyc index 497637e3..176b615d 100644 Binary files a/backend_service/src/__pycache__/main.cpython-313.pyc and b/backend_service/src/__pycache__/main.cpython-313.pyc differ diff --git a/backend_service/src/__pycache__/models.cpython-313.pyc b/backend_service/src/__pycache__/models.cpython-313.pyc index 13fe7a6f..92238477 100644 Binary files a/backend_service/src/__pycache__/models.cpython-313.pyc and b/backend_service/src/__pycache__/models.cpython-313.pyc differ 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 34987b70..eb7ca6b9 100644 Binary files a/backend_service/src/__pycache__/py_tree_generator.cpython-313.pyc and b/backend_service/src/__pycache__/py_tree_generator.cpython-313.pyc differ diff --git a/backend_service/src/__pycache__/websocket_manager.cpython-313.pyc b/backend_service/src/__pycache__/websocket_manager.cpython-313.pyc index 0878380b..939083a5 100644 Binary files a/backend_service/src/__pycache__/websocket_manager.cpython-313.pyc and b/backend_service/src/__pycache__/websocket_manager.cpython-313.pyc differ diff --git a/backend_service/src/llm/__init__.py b/backend_service/src/llm/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/backend_service/src/llm/__init__.py @@ -0,0 +1 @@ + diff --git a/backend_service/src/llm/__pycache__/__init__.cpython-313.pyc b/backend_service/src/llm/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..34c7f35c Binary files /dev/null and b/backend_service/src/llm/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend_service/src/llm/__pycache__/gateway.cpython-313.pyc b/backend_service/src/llm/__pycache__/gateway.cpython-313.pyc new file mode 100644 index 00000000..465ad5c8 Binary files /dev/null and b/backend_service/src/llm/__pycache__/gateway.cpython-313.pyc differ diff --git a/backend_service/src/llm/__pycache__/response_parser.cpython-313.pyc b/backend_service/src/llm/__pycache__/response_parser.cpython-313.pyc new file mode 100644 index 00000000..83a6af32 Binary files /dev/null and b/backend_service/src/llm/__pycache__/response_parser.cpython-313.pyc differ diff --git a/backend_service/src/llm/__pycache__/tool_runtime.cpython-313.pyc b/backend_service/src/llm/__pycache__/tool_runtime.cpython-313.pyc new file mode 100644 index 00000000..aa6f4ac2 Binary files /dev/null and b/backend_service/src/llm/__pycache__/tool_runtime.cpython-313.pyc differ diff --git a/backend_service/src/llm/gateway.py b/backend_service/src/llm/gateway.py new file mode 100644 index 00000000..4b11d073 --- /dev/null +++ b/backend_service/src/llm/gateway.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +import json +import logging +import os +from typing import Any, Dict, Optional, Tuple + +from openai import OpenAIError + +from .response_parser import parse_json_payload, unpack_message + + +class LLMGateway: + def __init__(self, generator: Any): + 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: + scene_mode = "scene1" + try: + 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}, + ], + temperature=0.0, + response_format={"type": "json_object"}, + max_tokens=self.generator.classifier_max_tokens, + extra_body={"chat_template_kwargs": {"enable_thinking": self.stage1_enable_thinking}}, + ) + class_str = classifier_resp.choices[0].message.content + class_obj = json.loads(class_str or "{}") + if isinstance(class_obj, dict) and class_obj.get("mode") in ("simple", "scene1", "scene4"): + scene_mode = class_obj.get("mode") + except Exception as exc: # noqa: BLE001 + logging.warning("场景分类失败,默认按scene1处理: %s", exc) + return scene_mode + + def generate_json(self, scene_mode: str, system_prompt: str, user_prompt: str, max_attempts: int = 3) -> Tuple[Dict[str, Any], Optional[str], str]: + is_simple = scene_mode == "simple" + client = self.generator.simple_llm_client if is_simple else self.generator.complex_llm_client + model_name = self.generator.simple_model if is_simple else self.generator.complex_model + max_tokens = self.generator.simple_max_tokens if is_simple else self.generator.complex_max_tokens + + for attempt in range(max_attempts): + try: + response = client.chat.completions.create( + model=model_name, + messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}], + temperature=0.0 if is_simple else 0.1, + response_format={"type": "json_object"}, + max_tokens=max_tokens, + extra_body={"chat_template_kwargs": {"enable_thinking": False}}, + ) + message = response.choices[0].message if hasattr(response.choices[0], "message") else response.choices[0].get("message") + content, reasoning_content, tool_calls = unpack_message(message) + if (content is None or str(content).strip() == "") and tool_calls: + logging.warning("模型仍在请求工具调用,尝试下一次生成。") + continue + parsed, reasoning_text, raw_text = parse_json_payload(content, reasoning_content) + return parsed, reasoning_text, raw_text + except (OpenAIError, json.JSONDecodeError, ValueError) as exc: + logging.warning("第 %d/%d 次生成失败: %s", attempt + 1, max_attempts, exc) + raise RuntimeError("在3次尝试后,仍未能生成一个有效的Pytree。") + diff --git a/backend_service/src/llm/response_parser.py b/backend_service/src/llm/response_parser.py new file mode 100644 index 00000000..515c00f9 --- /dev/null +++ b/backend_service/src/llm/response_parser.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +import json +import re +from typing import Any, Dict, Optional, Tuple + + +def unpack_message(message: Any) -> Tuple[Optional[str], Optional[str], Any]: + if message is None: + return None, None, None + if hasattr(message, "content"): + return getattr(message, "content", None), getattr(message, "reasoning_content", None), getattr(message, "tool_calls", None) + if isinstance(message, dict): + return message.get("content"), message.get("reasoning_content"), message.get("tool_calls") + return None, None, None + + +def parse_json_payload(content: Optional[str], reasoning_content: Optional[str]) -> Tuple[Dict[str, Any], Optional[str], str]: + combined_text = "" + if isinstance(reasoning_content, str) and reasoning_content.strip(): + combined_text += f"\n{reasoning_content}\n\n" + if isinstance(content, str) and content.strip(): + combined_text += content + + raw_text = combined_text or (content or "") + reasoning_text = None + payload_text = raw_text + match = re.search(r"([\s\S]*?)", raw_text) + if match: + reasoning_text = match.group(1).strip() + payload_text = re.sub(r"[\s\S]*?", "", raw_text).strip() + + parsed = json.loads(payload_text) + if not isinstance(parsed, dict): + raise ValueError("LLM output must be a JSON object") + return parsed, reasoning_text, raw_text + diff --git a/backend_service/src/llm/tool_runtime.py b/backend_service/src/llm/tool_runtime.py new file mode 100644 index 00000000..0a4116eb --- /dev/null +++ b/backend_service/src/llm/tool_runtime.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +import json +import re +from typing import Any, Dict, List, Optional + +from ..tools.coordinate_tools import calc_offset_enu, calc_offset_esu_direction_text + + +class ToolRuntime: + def get_tool_definitions(self) -> List[Dict[str, Any]]: + return [ + { + "type": "function", + "function": { + "name": "calc_offset_enu", + "description": "根据ENU坐标系基准点、方向和距离计算偏移后的坐标。", + "parameters": { + "type": "object", + "properties": { + "base": { + "type": "object", + "properties": {"x": {"type": "number"}, "y": {"type": "number"}, "z": {"type": "number"}}, + "required": ["x", "y", "z"], + }, + "direction": {"type": "string", "enum": ["east", "west", "north", "south", "up", "down"]}, + "distance": {"type": "number", "minimum": 0}, + }, + "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"}, + "distance": {"type": "number", "minimum": 0}, + }, + "required": ["base", "direction_text", "distance"], + }, + }, + }, + ] + + def normalize_tool_calls(self, tool_calls: List[Any]) -> List[Dict[str, Any]]: + normalized: List[Dict[str, Any]] = [] + for call in tool_calls: + if hasattr(call, "function"): + normalized.append( + { + "id": getattr(call, "id", None), + "name": call.function.name, + "arguments": call.function.arguments, + } + ) + elif isinstance(call, dict): + normalized.append( + { + "id": call.get("id"), + "name": (call.get("function") or {}).get("name"), + "arguments": (call.get("function") or {}).get("arguments"), + } + ) + return normalized + + def execute_tool_calls(self, tool_calls: List[Any]) -> List[Dict[str, Any]]: + tool_messages: List[Dict[str, Any]] = [] + for call in self.normalize_tool_calls(tool_calls): + tool_name = call.get("name") + tool_args = call.get("arguments") + tool_id = call.get("id") + if not tool_name: + continue + try: + args = json.loads(tool_args or "{}") + except json.JSONDecodeError: + continue + 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}"} + except Exception as exc: # noqa: BLE001 + result = {"error": str(exc)} + tool_messages.append({"role": "tool", "tool_call_id": tool_id, "content": json.dumps(result, ensure_ascii=False)}) + return tool_messages + + def should_enable_tools(self, user_prompt: str, location_context: str) -> bool: + if not user_prompt or not location_context: + return False + direction_patterns = ["东边", "西边", "南边", "北边", "东侧", "西侧", "南侧", "北侧", "东南", "西南", "东北", "西北", "南偏东", "南偏西", "北偏东", "北偏西"] + 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 + return has_direction and has_distance + + def build_precomputed_waypoint(self, user_prompt: str, location_context: str) -> Optional[Dict[str, float]]: + if not self.should_enable_tools(user_prompt, location_context): + return None + base_match = re.search( + r"[\"']?x[\"']?\s*[:=]\s*(-?\d+(?:\.\d+)?)\D+[\"']?y[\"']?\s*[:=]\s*(-?\d+(?:\.\d+)?)\D+[\"']?z[\"']?\s*[:=]\s*(-?\d+(?:\.\d+)?)", + location_context, + re.IGNORECASE | re.DOTALL, + ) + if not base_match: + return None + base = {"x": float(base_match.group(1)), "y": float(base_match.group(2)), "z": float(base_match.group(3))} + dist_match = re.search(r"(\d+(?:\.\d+)?)\s*(米|m)", user_prompt) + if not dist_match: + return None + distance = float(dist_match.group(1)) + + simple_direction_map = { + "东": "east", + "西": "west", + "南": "south", + "北": "north", + "上": "up", + "下": "down", + } + for zh, en in simple_direction_map.items(): + if f"{zh}边" in user_prompt or f"{zh}侧" in user_prompt or f"往{zh}" in user_prompt or f"向{zh}" in user_prompt: + return calc_offset_enu(base=base, direction=en, distance=distance) + + complex_patterns = ["东南", "西南", "东北", "西北", "南偏东", "南偏西", "北偏东", "北偏西"] + for pattern in complex_patterns: + if pattern in user_prompt: + direction_match = re.search(r"(东南|西南|东北|西北|南偏东\d+(?:\.\d+)?度?|南偏西\d+(?:\.\d+)?度?|北偏东\d+(?:\.\d+)?度?|北偏西\d+(?:\.\d+)?度?)", user_prompt) + if direction_match: + return calc_offset_esu_direction_text(base=base, direction_text=direction_match.group(1), distance=distance) + return None + diff --git a/backend_service/src/pipeline/__init__.py b/backend_service/src/pipeline/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/backend_service/src/pipeline/__init__.py @@ -0,0 +1 @@ + diff --git a/backend_service/src/pipeline/__pycache__/__init__.cpython-313.pyc b/backend_service/src/pipeline/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..ae7656d8 Binary files /dev/null and b/backend_service/src/pipeline/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend_service/src/pipeline/__pycache__/contracts.cpython-313.pyc b/backend_service/src/pipeline/__pycache__/contracts.cpython-313.pyc new file mode 100644 index 00000000..306c8139 Binary files /dev/null and b/backend_service/src/pipeline/__pycache__/contracts.cpython-313.pyc differ diff --git a/backend_service/src/pipeline/__pycache__/orchestrator.cpython-313.pyc b/backend_service/src/pipeline/__pycache__/orchestrator.cpython-313.pyc new file mode 100644 index 00000000..27eec054 Binary files /dev/null and b/backend_service/src/pipeline/__pycache__/orchestrator.cpython-313.pyc differ diff --git a/backend_service/src/pipeline/__pycache__/stages.cpython-313.pyc b/backend_service/src/pipeline/__pycache__/stages.cpython-313.pyc new file mode 100644 index 00000000..37847e6d Binary files /dev/null and b/backend_service/src/pipeline/__pycache__/stages.cpython-313.pyc differ diff --git a/backend_service/src/pipeline/contracts.py b/backend_service/src/pipeline/contracts.py new file mode 100644 index 00000000..d0ecf8ea --- /dev/null +++ b/backend_service/src/pipeline/contracts.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from typing import Any, Dict, List, Literal, Optional + +from pydantic import BaseModel, Field + + +SceneMode = Literal["simple", "scene1", "scene4"] + + +class TaskUnderstanding(BaseModel): + scene_mode: SceneMode = "scene1" + intent_type: str = "generic_mission" + requires_relative_target: bool = False + entities: Dict[str, Any] = Field(default_factory=dict) + risk_flags: List[str] = Field(default_factory=list) + constraints: Dict[str, Any] = Field(default_factory=dict) + + +class ContextBinding(BaseModel): + location_context: str = "" + pattern_context: str = "" + rules_context: str = "" + citations: Dict[str, List[str]] = Field(default_factory=dict) + resolved_refs: Dict[str, Any] = Field(default_factory=dict) + precomputed_waypoints: List[Dict[str, float]] = Field(default_factory=list) + relative_refs: List[Dict[str, Any]] = Field(default_factory=list) + required_actions: List[str] = Field(default_factory=list) + + +class BTDraft(BaseModel): + system_prompt: str + user_prompt: str + allowed_nodes: Dict[str, List[str]] = Field(default_factory=dict) + llm_raw_json: Dict[str, Any] + reasoning_text: Optional[str] = None + final_prompt: str + diff --git a/backend_service/src/pipeline/orchestrator.py b/backend_service/src/pipeline/orchestrator.py new file mode 100644 index 00000000..9bac1da4 --- /dev/null +++ b/backend_service/src/pipeline/orchestrator.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from typing import Any, Dict + +from .stages import PipelineStages + + +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) + context = self.stages.stage2_context_binding(user_prompt, understanding) + draft = self.stages.stage3_bt_planning(user_prompt, understanding, context) + return self.stages.stage4_validate_and_postprocess(user_prompt, understanding, context, draft) + diff --git a/backend_service/src/pipeline/stages.py b/backend_service/src/pipeline/stages.py new file mode 100644 index 00000000..56c8e935 --- /dev/null +++ b/backend_service/src/pipeline/stages.py @@ -0,0 +1,165 @@ +from __future__ import annotations + +import json +import logging +import re +import uuid +from typing import Any, Dict, List + +from .contracts import BTDraft, ContextBinding, TaskUnderstanding + + +def _infer_intent_type(user_prompt: str, scene_mode: str) -> str: + text = user_prompt or "" + if scene_mode == "simple": + return "single_action" + if any(k in text for k in ["拍照", "拍个照片", "拍张照"]): + return "search_and_photo" + if any(k in text for k in ["监控", "巡视", "巡查", "侦察"]): + return "patrol_or_monitor" + if any(k in text for k in ["返航", "回到", "降落"]): + return "return_or_land" + return "generic_mission" + + +def _extract_risk_flags(user_prompt: str) -> List[str]: + flags: List[str] = [] + if any(k in user_prompt for k in ["我确认", "等待确认", "经允许", "我通过后"]): + flags.append("needs_manual_confirmation") + if any(k in user_prompt for k in ["贴近", "靠近", "飞近"]): + flags.append("needs_approach_target") + if any(k in user_prompt for k in ["紧急", "立即返航"]): + flags.append("needs_emergency_return_rule") + if any(k in user_prompt for k in ["面前", "左边", "右边", "后方", "前方", "这栋楼"]): + flags.append("relative_reference_detected") + return flags + + +def _derive_required_actions(intent_type: str, scene_mode: str, risk_flags: List[str]) -> List[str]: + actions = {"takeoff", "land", "fly_to_waypoint", "move_direction"} + if scene_mode != "simple": + actions.update({"rotate_search", "object_detect", "object_detected", "take_photos", "Sequence", "Selector"}) + if intent_type == "return_or_land": + actions.add("return_emergency") + if "needs_manual_confirmation" in risk_flags: + actions.add("manual_confirmation") + if "needs_approach_target" in risk_flags: + actions.add("approach_target") + return sorted(actions) + + +def _extract_relative_refs(user_prompt: str) -> List[Dict[str, Any]]: + refs: List[Dict[str, Any]] = [] + relation_map = {"面前": "front", "前方": "front", "左边": "left", "右边": "right", "后方": "behind"} + for token, relation in relation_map.items(): + if token in user_prompt: + distance = None + match = re.search(rf"{token}[^0-9]*(\d+(?:\.\d+)?)\s*(米|m)", user_prompt) + if match: + distance = float(match.group(1)) + refs.append( + { + "anchor": "front_building" if "楼" in user_prompt else "current_heading_ref", + "relation": relation, + "distance_m": distance, + } + ) + return refs + + +class PipelineStages: + def __init__(self, generator: Any): + self.generator = generator + + def stage1_task_understanding(self, user_prompt: str) -> TaskUnderstanding: + scene_mode = self.generator.llm_gateway.classify_scene(user_prompt) + intent_type = _infer_intent_type(user_prompt, scene_mode) + risk_flags = _extract_risk_flags(user_prompt) + requires_relative = "relative_reference_detected" in risk_flags + entities = {"raw_prompt": user_prompt} + return TaskUnderstanding( + scene_mode=scene_mode, + intent_type=intent_type, + requires_relative_target=requires_relative, + entities=entities, + risk_flags=risk_flags, + constraints={}, + ) + + def stage2_context_binding(self, user_prompt: str, understanding: TaskUnderstanding) -> ContextBinding: + scopes = ["location"] + if understanding.scene_mode != "simple": + scopes.append("pattern") + if understanding.risk_flags: + scopes.append("rules") + + retrieved = self.generator.retriever.retrieve(user_prompt, scopes=scopes, n_results=3) + precomputed = [] + waypoint = self.generator.tool_runtime.build_precomputed_waypoint(user_prompt, retrieved.get("location_context", "")) + if waypoint: + precomputed.append(waypoint) + relative_refs = _extract_relative_refs(user_prompt) if understanding.requires_relative_target else [] + required_actions = _derive_required_actions(understanding.intent_type, understanding.scene_mode, understanding.risk_flags) + + return ContextBinding( + location_context=retrieved.get("location_context", ""), + pattern_context=retrieved.get("pattern_context", ""), + rules_context=retrieved.get("rules_context", ""), + citations=retrieved.get("citations", {}), + resolved_refs={}, + precomputed_waypoints=precomputed, + relative_refs=relative_refs, + required_actions=required_actions, + ) + + def stage3_bt_planning(self, user_prompt: str, understanding: TaskUnderstanding, context: ContextBinding) -> BTDraft: + include_extra_examples = understanding.intent_type == "generic_mission" + package = self.generator.prompt_composer.compose( + scene_mode=understanding.scene_mode, + intent_type=understanding.intent_type, + required_actions=context.required_actions, + risk_flags=understanding.risk_flags, + context_blocks={ + "location": context.location_context, + "pattern": context.pattern_context, + "rules": context.rules_context, + }, + include_extra_examples=include_extra_examples, + ) + final_user_prompt = user_prompt + (package.user_augmentation or "") + payload, reasoning_text, _raw_text = self.generator.llm_gateway.generate_json( + scene_mode=understanding.scene_mode, + system_prompt=package.system_prompt, + user_prompt=final_user_prompt, + ) + final_prompt = f"=== System Prompt ===\n{package.system_prompt}\n\n=== User Prompt ===\n{final_user_prompt}" + return BTDraft( + system_prompt=package.system_prompt, + user_prompt=final_user_prompt, + allowed_nodes=package.allowed_nodes, + llm_raw_json=payload, + reasoning_text=reasoning_text, + final_prompt=final_prompt, + ) + + def stage4_validate_and_postprocess(self, user_prompt: str, understanding: TaskUnderstanding, context: ContextBinding, draft: BTDraft) -> Dict[str, Any]: + payload = dict(draft.llm_raw_json) + if context.relative_refs and understanding.scene_mode != "simple": + payload.setdefault("context", {}) + payload["context"]["relative_refs"] = context.relative_refs + if context.precomputed_waypoints: + payload["context"]["resolved_refs"] = { + "strategy": "backend_static_resolution", + "waypoints": context.precomputed_waypoints, + } + + self.generator.validator.validate(understanding.scene_mode, payload) + payload["plan_id"] = str(uuid.uuid4()) + payload["visualization_url"] = self.generator.render_visualization(payload, understanding.scene_mode) + payload["final_prompt"] = draft.final_prompt + if draft.reasoning_text: + self.generator.save_reasoning_content(draft.reasoning_text) + self.generator._save_history(user_prompt, payload) + logging.info("✅ 成功生成并验证了Pytree(Pipeline)") + return payload + diff --git a/backend_service/src/prompting/__init__.py b/backend_service/src/prompting/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/backend_service/src/prompting/__init__.py @@ -0,0 +1 @@ + diff --git a/backend_service/src/prompting/__pycache__/__init__.cpython-313.pyc b/backend_service/src/prompting/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..cea573b5 Binary files /dev/null and b/backend_service/src/prompting/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend_service/src/prompting/__pycache__/composer.cpython-313.pyc b/backend_service/src/prompting/__pycache__/composer.cpython-313.pyc new file mode 100644 index 00000000..1c6c2601 Binary files /dev/null and b/backend_service/src/prompting/__pycache__/composer.cpython-313.pyc differ diff --git a/backend_service/src/prompting/__pycache__/manifest_loader.cpython-313.pyc b/backend_service/src/prompting/__pycache__/manifest_loader.cpython-313.pyc new file mode 100644 index 00000000..e15ff2fb Binary files /dev/null and b/backend_service/src/prompting/__pycache__/manifest_loader.cpython-313.pyc differ diff --git a/backend_service/src/prompting/composer.py b/backend_service/src/prompting/composer.py new file mode 100644 index 00000000..074b52f4 --- /dev/null +++ b/backend_service/src/prompting/composer.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +import json +import os +from dataclasses import dataclass +from functools import lru_cache +from typing import Dict, List, Sequence, Set + +from .manifest_loader import ManifestPromptLoader + + +@dataclass +class PromptPackage: + system_prompt: str + user_augmentation: str + allowed_nodes: Dict[str, List[str]] + + +class PromptComposer: + def __init__(self, prompts_dir: str): + self.loader = ManifestPromptLoader(prompts_dir) + self.prompts_dir = prompts_dir + self._simple_prompt = self.loader.load_text_file("simple_mode_prompt.txt") + + @lru_cache(maxsize=64) + def _cached_scene_parts(self, scene_mode: str) -> tuple[str, str, str, str]: + # 固定骨架 + header = self._load_partial("header.txt") + required_fields = self._load_partial("required_fields.txt") + standard_template = self._load_partial("standard_template.txt") + examples = self._load_partial("scene1_examples.txt" if scene_mode == "scene1" else "scene4_examples.txt") + return header, required_fields, standard_template, examples + + def _load_partial(self, file_name: str) -> str: + path = os.path.join(self.prompts_dir, "partials", file_name) + if not os.path.exists(path): + return "" + return self.loader.load_fragment_text(path).strip("\n") + + def _load_nodes_payload(self) -> Dict[str, List[Dict[str, str]]]: + core_path = os.path.join(self.prompts_dir, "partials", "core_nodes.json") + if not os.path.exists(core_path): + return {"actions": [], "conditions": [], "control_flow": [], "decorators": []} + with open(core_path, "r", encoding="utf-8") as f: + payload = json.load(f) + return payload + + def _slice_nodes(self, required_actions: Sequence[str], risk_flags: Sequence[str], scene_mode: str) -> Dict[str, List[str]]: + payload = self._load_nodes_payload() + base_actions: Set[str] = {"takeoff", "land", "fly_to_waypoint", "move_direction"} + base_conditions: Set[str] = {"object_detected"} + if scene_mode != "simple": + base_actions.update({"rotate_search", "take_photos"}) + action_names = {a.get("name") for a in payload.get("actions", []) if isinstance(a, dict)} + condition_names = {c.get("name") for c in payload.get("conditions", []) if isinstance(c, dict)} + + selected_actions = set(required_actions) | (base_actions & action_names) + selected_conditions = base_conditions & condition_names + if "needs_manual_confirmation" in risk_flags and "manual_confirmation" in action_names: + selected_actions.add("manual_confirmation") + if "needs_emergency_return_rule" in risk_flags and "return_emergency" in action_names: + selected_actions.add("return_emergency") + if "needs_approach_target" in risk_flags and "approach_target" in action_names: + selected_actions.add("approach_target") + + ordered_actions = sorted(selected_actions)[:30] + ordered_conditions = sorted(selected_conditions)[:30] + return {"actions": ordered_actions, "conditions": ordered_conditions} + + def _build_nodes_snippet(self, selected: Dict[str, List[str]]) -> str: + payload = self._load_nodes_payload() + selected_action_set = set(selected.get("actions", [])) + selected_condition_set = set(selected.get("conditions", [])) + slim_payload = { + "actions": [a for a in payload.get("actions", []) if a.get("name") in selected_action_set], + "conditions": [c for c in payload.get("conditions", []) if c.get("name") in selected_condition_set], + "control_flow": payload.get("control_flow", []), + "decorators": payload.get("decorators", []), + } + return "\n".join( + [ + "## 一、核心节点定义(裁剪后)", + "#### 1. 可用节点定义 (必须遵守)", + "```json", + json.dumps(slim_payload, ensure_ascii=False, indent=2), + "```", + ] + ) + + def compose( + self, + scene_mode: str, + intent_type: str, + required_actions: Sequence[str], + risk_flags: Sequence[str], + context_blocks: Dict[str, str], + include_extra_examples: bool, + ) -> PromptPackage: + if scene_mode == "simple": + user_aug = self._build_user_augmentation(context_blocks) + return PromptPackage(system_prompt=self._simple_prompt, user_augmentation=user_aug, allowed_nodes={}) + + header, required_fields, standard_template, examples = self._cached_scene_parts(scene_mode) + selected_nodes = self._slice_nodes(required_actions, risk_flags, scene_mode) + node_snippet = self._build_nodes_snippet(selected_nodes) + + parts: List[str] = [header, node_snippet, required_fields, standard_template, examples] + common_rules = self._load_partial("common_rules.txt") + if common_rules: + parts.append(common_rules) + if include_extra_examples: + extra = self._load_partial("system_extra_examples.txt") + if extra: + parts.append(extra) + parts.append(f"## 任务意图标签\n- intent_type: `{intent_type}`") + system_prompt = "\n\n".join(p for p in parts if p).strip() + return PromptPackage(system_prompt=system_prompt, user_augmentation=self._build_user_augmentation(context_blocks), allowed_nodes=selected_nodes) + + def _build_user_augmentation(self, context_blocks: Dict[str, str]) -> str: + ordered = [("地点知识", "location"), ("任务模式", "pattern"), ("规则知识", "rules")] + chunks: List[str] = [] + for label, key in ordered: + value = (context_blocks.get(key) or "").strip() + if value: + chunks.append(f"【{label}】\n{value}") + if not chunks: + return "" + return "\n\n---\n参考知识:\n" + "\n\n".join(chunks) + "\n---" + diff --git a/backend_service/src/prompting/manifest_loader.py b/backend_service/src/prompting/manifest_loader.py new file mode 100644 index 00000000..1de21beb --- /dev/null +++ b/backend_service/src/prompting/manifest_loader.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import json +import os +from typing import Dict, List, Optional + +import yaml + + +class ManifestPromptLoader: + def __init__(self, prompts_dir: str): + self.prompts_dir = prompts_dir + self._manifest_cache: Optional[dict] = None + self._manifest_mtime: Optional[float] = None + + def load_manifest(self) -> Optional[dict]: + manifest_path = os.path.join(self.prompts_dir, "prompt_manifest.yaml") + try: + mtime = os.path.getmtime(manifest_path) + except FileNotFoundError: + return None + + if self._manifest_cache is not None and self._manifest_mtime == mtime: + return self._manifest_cache + + with open(manifest_path, "r", encoding="utf-8") as f: + manifest = yaml.safe_load(f) or {} + if not isinstance(manifest, dict): + return None + self._manifest_cache = manifest + self._manifest_mtime = mtime + return manifest + + def load_text_file(self, file_name: str) -> str: + path = os.path.join(self.prompts_dir, file_name) + try: + with open(path, "r", encoding="utf-8") as f: + return f.read() + except FileNotFoundError: + return "" + + def resolve_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"): + with open(fragment_path, "r", encoding="utf-8") as f: + payload = json.load(f) + 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_scene_fragments(self, scene_key: str) -> List[str]: + manifest = self.load_manifest() or {} + scene_map = manifest.get("scenes", manifest) + fragments = scene_map.get(scene_key, []) + if not isinstance(fragments, list): + return [] + return [f for f in fragments if isinstance(f, str)] + diff --git a/backend_service/src/py_tree_generator.py b/backend_service/src/py_tree_generator.py index 11244890..119e022d 100644 --- a/backend_service/src/py_tree_generator.py +++ b/backend_service/src/py_tree_generator.py @@ -6,15 +6,20 @@ import re from typing import Dict, Any, Optional, Set, List import chromadb import openai -from openai import OpenAIError -import jsonschema import requests import platform # 新增:用于选择合适的中文字体 import yaml -from .tools.coordinate_tools import calc_offset_enu, calc_offset_esu_direction_text +from .pipeline.orchestrator import GenerationOrchestrator +from .llm.gateway import LLMGateway +from .llm.tool_runtime import ToolRuntime +from .prompting.composer import PromptComposer +from .retrieval.adapters.chroma_adapter import ChromaAdapter +from .retrieval.retriever import UnifiedRetriever +from .validation.schema_provider import SchemaProvider +from .validation.validator import PytreeValidator # --- 自定义远程嵌入函数 (与ingest.py中定义一致) --- -from chromadb.api.types import Documents, EmbeddingFunction, Embeddings, Embeddable +from chromadb.api.types import EmbeddingFunction, Embeddings, Embeddable class RemoteEmbeddingFunction(EmbeddingFunction[Embeddable]): def __init__(self, api_url: str): self._api_url = api_url @@ -45,6 +50,17 @@ logging.basicConfig( ] ) + +class _NullRetriever: + def retrieve(self, query: str, scopes: List[str], n_results: int = 3) -> Dict[str, Any]: + _ = (query, scopes, n_results) + return { + "location_context": "", + "pattern_context": "", + "rules_context": "", + "citations": {}, + } + # ============================================================================== # VALIDATION LOGIC (from utils/validation.py) # ============================================================================== @@ -392,34 +408,6 @@ def _generate_simple_mode_schema(allowed_actions: set) -> dict: } return schema -def _validate_pytree_with_schema(pytree_instance: dict, schema: dict) -> bool: - """ - 使用JSON Schema验证给定的Pytree实例。 - """ - try: - jsonschema.validate(instance=pytree_instance, schema=schema) - logging.info("✅ JSON Schema验证成功") - - return True - except jsonschema.ValidationError as e: - logging.warning("❌ Pytree验证失败") - logging.warning(f"错误信息: {e.message}") - error_path = list(e.path) - logging.warning(f"错误路径: {' -> '.join(map(str, error_path)) if error_path else '根节点'}") - - # 提供更具体的错误信息 - if "object_detect" in str(e.message) or "object_detected" in str(e.message): - 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 - except Exception as e: - logging.error(f"进行JSON Schema验证时发生未知错误: {e}") - return False - # ============================================================================== # VISUALIZATION LOGIC (from utils/visualization.py) # ============================================================================== @@ -628,20 +616,32 @@ class PyTreeGenerator: # --- ChromaDB Client Setup --- vector_store_path = os.path.abspath(os.path.join(self.base_dir, '..', '..', 'tools', 'rag','vector_store')) - self.chroma_client = chromadb.PersistentClient(path=vector_store_path) - - # Explicitly use the remote embedding function for queries embedding_api_url = f"http://{self.orin_ip}:8090/v1/embeddings" embedding_func = RemoteEmbeddingFunction(api_url=embedding_api_url) - self.collection = self.chroma_client.get_collection( - name="drone_docs", - embedding_function=embedding_func - ) + self.chroma_client = None + self.collection = None + try: + self.chroma_client = chromadb.PersistentClient(path=vector_store_path) + self.collection = self.chroma_client.get_collection( + name="drone_docs", + embedding_function=embedding_func + ) + except BaseException as exc: + logging.error(f"ChromaDB 初始化失败,将使用空检索器: {exc}") # 使用复杂模式提示词作为节点来源,确保Schema稳定 allowed_actions, allowed_conditions = _parse_allowed_nodes_from_prompt(self.complex_prompt) self.schema = _generate_pytree_schema(allowed_actions, allowed_conditions) self.simple_schema = _generate_simple_mode_schema(allowed_actions) + self.llm_gateway = LLMGateway(self) + self.tool_runtime = ToolRuntime() + self.prompt_composer = PromptComposer(self.prompts_dir) + if self.chroma_client is not None: + self.retriever = UnifiedRetriever(ChromaAdapter(self.chroma_client), embedding_func) + else: + self.retriever = _NullRetriever() + self.validator = PytreeValidator(SchemaProvider(self.schema, self.simple_schema)) + self.orchestrator = GenerationOrchestrator(self) def _load_prompt(self, file_name: str) -> str: try: @@ -742,174 +742,6 @@ class PyTreeGenerator: return "\n\n".join(contents).strip("\n") - def _get_tool_definitions(self) -> list[dict]: - return [ - { - "type": "function", - "function": { - "name": "calc_offset_enu", - "description": "根据ENU坐标系基准点、方向和距离计算偏移后的坐标。", - "parameters": { - "type": "object", - "properties": { - "base": { - "type": "object", - "properties": { - "x": {"type": "number"}, - "y": {"type": "number"}, - "z": {"type": "number"} - }, - "required": ["x", "y", "z"] - }, - "direction": { - "type": "string", - "enum": [ - "east", "west", "north", "south", - "up", "down" - ] - }, - "distance": {"type": "number", "minimum": 0} - }, - "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"] - } - } - } - ] - - def _normalize_tool_calls(self, tool_calls: list) -> list[dict]: - normalized = [] - for call in tool_calls: - if hasattr(call, "function"): - normalized.append( - { - "id": getattr(call, "id", None), - "name": call.function.name, - "arguments": call.function.arguments, - } - ) - elif isinstance(call, dict): - normalized.append( - { - "id": call.get("id"), - "name": (call.get("function") or {}).get("name"), - "arguments": (call.get("function") or {}).get("arguments"), - } - ) - return normalized - - def _execute_tool_calls(self, tool_calls: list) -> list[dict]: - tool_messages = [] - normalized = self._normalize_tool_calls(tool_calls) - for call in normalized: - tool_name = call.get("name") - tool_args = call.get("arguments") - tool_id = call.get("id") - if not tool_name: - logging.warning("工具调用缺少名称,已忽略。") - continue - try: - args = json.loads(tool_args or "{}") - except json.JSONDecodeError: - logging.warning(f"工具调用参数解析失败: {tool_args}") - continue - 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( - { - "role": "tool", - "tool_call_id": tool_id, - "content": json.dumps(result, ensure_ascii=False) - } - ) - except Exception as exc: - logging.warning(f"工具调用执行失败({tool_name}): {exc}") - tool_messages.append( - { - "role": "tool", - "tool_call_id": tool_id, - "content": json.dumps({"error": str(exc)}, ensure_ascii=False) - } - ) - return tool_messages - - def _retrieve_context(self, query: str) -> Optional[str]: - logging.info("--- 开始从向量数据库检索上下文 ---") - try: - results = self.collection.query(query_texts=[query], n_results=5) - retrieved_docs = results.get("documents", [[]])[0] - if not retrieved_docs: - logging.warning("在向量数据库中没有找到相关的上下文信息。") - return None - context_str = "\n\n".join(retrieved_docs) - logging.info("--- 成功检索到上下文信息 ---") - # 打印检索到的上下文内容 - logging.info(f"📚 检索到的上下文内容:\n{context_str}") - return context_str - except Exception as e: - logging.error(f"从向量数据库检索时发生错误: {e}") - return None - - def _should_enable_tools(self, user_prompt: str, retrieved_context: Optional[str]) -> bool: - if not user_prompt: - return False - - direction_patterns = [ - "东边", "西边", "南边", "北边", - "东侧", "西侧", "南侧", "北侧", - "往东", "往西", "往南", "往北", - "向东", "向西", "向南", "向北", - "东南", "西南", "东北", "西北", - "南偏东", "南偏西", "北偏东", "北偏西", - ] - 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 - if not (has_direction and has_distance): - return False - - if not retrieved_context: - return False - - place_candidates: list[str] = [] - for line in retrieved_context.splitlines(): - if "地点:" in line or "别名:" in line: - place_candidates.extend(re.findall(r"'([^']+)'", line)) - if not place_candidates: - return False - - return any(place in user_prompt for place in place_candidates) - def _save_history(self, prompt: str, result_dict: dict): """保存请求和响应历史记录""" import datetime @@ -934,316 +766,31 @@ class PyTreeGenerator: except Exception as e: logging.error(f"保存历史记录失败: {e}") - async def generate(self, user_prompt: str) -> Dict[str, Any]: - """ - Generates a py_tree.json structure based on the user's prompt. - """ - logging.info(f"接收到用户请求: {user_prompt}") - - # 第一步:场景分类(simple/scene1/scene4) - scene_mode = "scene1" - try: - classifier_resp = self.classifier_client.chat.completions.create( - model=self.classifier_model, - messages=[ - {"role": "system", "content": self.scene_classifier_prompt or "你是一个分类器,只输出JSON。"}, - {"role": "user", "content": user_prompt} - ], - temperature=0.0, - response_format={"type": "json_object"}, # 强制JSON输出,禁用思考功能 - max_tokens=self.classifier_max_tokens, - # 禁用 Qwen3 模型的思考功能(通过 extra_body 传递) - # 注意:如果 API 服务器不支持此参数,会忽略 - extra_body={"chat_template_kwargs": {"enable_thinking": False}} - ) - class_str = classifier_resp.choices[0].message.content - class_obj = json.loads(class_str) - if isinstance(class_obj, dict) and class_obj.get("mode") in ("simple", "scene1", "scene4"): - scene_mode = class_obj.get("mode") - logging.info(f"场景分类结果: {scene_mode}") - except Exception as e: - logging.warning(f"场景分类失败,默认按scene1处理: {e}") - - # 第二步:根据模式准备提示词与上下文(简单与复杂都执行检索增强) - # 基于场景选择提示词;非simple时追加强制规则,避免模型误输出简单结构 + def render_visualization(self, payload: Dict[str, Any], scene_mode: str) -> str: + vis_filename = "py_tree.png" + vis_path = os.path.join(self.vis_dir, vis_filename) + root_node = payload.get("root", {}) if scene_mode == "simple": - use_prompt = self.simple_prompt - elif scene_mode == "scene4": - use_prompt = self.scene4_prompt + _visualize_pytree(root_node, os.path.splitext(vis_path)[0]) else: - use_prompt = self.scene1_prompt - if scene_mode != "simple": - use_prompt = ( - (use_prompt or self.complex_prompt or "") + - "\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。" - ) - final_user_prompt = user_prompt - retrieved_context = self._retrieve_context(user_prompt) - if retrieved_context: - augmentation = ( - "\n\n---\n" - "参考知识:\n" - "以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n" - f"{retrieved_context}" - "\n---" - ) - final_user_prompt += augmentation - else: - logging.warning("未检索到上下文或检索失败,将使用原始用户提示词。") - - # 构建完整的 final_prompt(准确反映实际发送给大模型的内容结构) - # 注意:RAG检索结果被添加到 user prompt 中,而不是 system prompt - # System Prompt: use_prompt(不包含RAG结果) - # User Prompt: final_user_prompt(包含原始user_prompt + RAG检索结果) - final_prompt = f"=== System Prompt ===\n{use_prompt}\n\n=== User Prompt ===\n{final_user_prompt}" - tool_enabled = self._should_enable_tools(user_prompt, retrieved_context) - for attempt in range(3): - logging.info(f"--- 第 {attempt + 1}/3 次尝试生成Pytree ---") - try: - # 简单/复杂分流到不同模型与提示词 - is_simple = scene_mode == "simple" - client = self.simple_llm_client if is_simple else self.complex_llm_client - model_name = self.simple_model if is_simple else self.complex_model - messages = [ - {"role": "system", "content": use_prompt}, - {"role": "user", "content": final_user_prompt} - ] - # 始终强制JSON响应并禁用思考功能 - response_kwargs = { - "model": model_name, - "messages": messages, - "temperature": 0.0 if is_simple else 0.1, - "response_format": {"type": "json_object"}, # 始终强制JSON输出,禁用思考功能 - "extra_body": {"chat_template_kwargs": {"enable_thinking": False}} - } - if tool_enabled: - response_kwargs["tools"] = self._get_tool_definitions() - response_kwargs["tool_choice"] = "auto" - # 基于模式设定最大输出token数(直接在代码中配置) - response_kwargs["max_tokens"] = self.simple_max_tokens if is_simple else self.complex_max_tokens - response = client.chat.completions.create(**response_kwargs) + _visualize_pytree(root_node, os.path.splitext(vis_path)[0]) + return f"/static/{vis_filename}" - # 工具调用处理:执行工具并回填后,强制模型输出JSON - for tool_round in range(3): - try: - first_msg = response.choices[0].message - tool_calls = getattr(first_msg, "tool_calls", None) - except Exception: - first_msg = response.choices[0].get("message") if isinstance(response.choices[0], dict) else None - tool_calls = (first_msg or {}).get("tool_calls") + def save_reasoning_content(self, reasoning_text: str) -> None: + try: + reasoning_path = os.path.join(self.reasoning_dir, "reasoning_content.md") + with open(reasoning_path, "w", encoding="utf-8") as rf: + rf.write(reasoning_text) + logging.info(f"📝 推理链已保存: {reasoning_path}") + lines = reasoning_text.splitlines() + preview = "\n".join(lines[: self.reasoning_preview_lines]) + logging.info("🧠 推理链预览(前%d行):\n%s", self.reasoning_preview_lines, preview) + except Exception as e: + logging.warning(f"保存推理链Markdown失败: {e}") - if not tool_calls: - break - - logging.info("检测到工具调用,执行第 %d 轮工具回填。", tool_round + 1) - tool_messages = self._execute_tool_calls(tool_calls) - if not tool_messages: - break - - tool_calls_for_messages = [] - for call in self._normalize_tool_calls(tool_calls): - if not call.get("id") or not call.get("name"): - continue - tool_calls_for_messages.append( - { - "id": call["id"], - "type": "function", - "function": { - "name": call["name"], - "arguments": call.get("arguments", "") - } - } - ) - messages = messages + [ - { - "role": "assistant", - "content": getattr(first_msg, "content", "") if first_msg else "", - "tool_calls": tool_calls_for_messages - } - ] + tool_messages - followup_kwargs = dict(response_kwargs) - followup_kwargs["messages"] = messages - followup_kwargs.pop("tools", None) - followup_kwargs.pop("tool_choice", None) - response = client.chat.completions.create(**followup_kwargs) - break - # 兼容可能存在的 reasoning_content 字段 - try: - msg = response.choices[0].message - msg_content = getattr(msg, "content", None) - msg_reasoning = getattr(msg, "reasoning_content", None) - remaining_tool_calls = getattr(msg, "tool_calls", None) - except Exception: - msg = response.choices[0]["message"] if isinstance(response.choices[0], dict) else None - msg_content = (msg or {}).get("content") if isinstance(msg, dict) else None - msg_reasoning = (msg or {}).get("reasoning_content") if isinstance(msg, dict) else None - remaining_tool_calls = (msg or {}).get("tool_calls") if isinstance(msg, dict) else None - - if (msg_content is None or str(msg_content).strip() == "") and remaining_tool_calls: - logging.warning("模型仍在请求工具调用,未返回JSON内容,重试下一次。") - continue - - combined_text = "" - if isinstance(msg_reasoning, str) and msg_reasoning.strip(): - # 将 reasoning_content 包装为 ,便于统一解析 - combined_text += f"\n{msg_reasoning}\n\n" - if isinstance(msg_content, str) and msg_content.strip(): - combined_text += msg_content - pytree_str = combined_text if combined_text else (msg_content or "") - raw_full_text_for_logging = pytree_str # 保存完整原文(含 )以便失败时完整打印 - - # 提取 推理链内容(若有) - reasoning_text = None - try: - think_match = re.search(r"([\s\S]*?)", pytree_str) - if think_match: - reasoning_text = think_match.group(1).strip() - # 去除推理文本后再尝试解析JSON - pytree_str = re.sub(r"[\s\S]*?", "", pytree_str).strip() - except Exception: - reasoning_text = None - # 单独捕获JSON解析错误并打印原始响应 - try: - pytree_dict = json.loads(pytree_str) - except json.JSONDecodeError as e: - logging.error(f"❌ JSON解析失败(第 {attempt + 1}/3 次)。\n—— 完整原始文本(含) ——\n{raw_full_text_for_logging}") - # 尝试打印响应对象的完整结构 - try: - raw_response_dump = None - if hasattr(response, 'model_dump_json'): - raw_response_dump = response.model_dump_json(indent=2, exclude_none=False) - elif hasattr(response, 'dict'): - raw_response_dump = json.dumps(response.dict(), ensure_ascii=False, indent=2, default=str) - else: - # 兜底:尝试将choices与关键字段展开 - safe_obj = { - "id": getattr(response, 'id', None), - "model": getattr(response, 'model', None), - "object": getattr(response, 'object', None), - "usage": getattr(response, 'usage', None), - "choices": [ - { - "index": getattr(c, 'index', None), - "finish_reason": getattr(c, 'finish_reason', None), - "message": { - "role": getattr(getattr(c, 'message', None), 'role', None), - "content": getattr(getattr(c, 'message', None), 'content', None), - "reasoning_content": getattr(getattr(c, 'message', None), 'reasoning_content', None) - } if getattr(c, 'message', None) is not None else None - } - for c in getattr(response, 'choices', []) - ] if hasattr(response, 'choices') else None - } - raw_response_dump = json.dumps(safe_obj, ensure_ascii=False, indent=2, default=str) - logging.error(f"—— 完整响应对象 ——\n{raw_response_dump}") - except Exception as dump_e: - try: - logging.error(f"响应对象转储失败,repr如下:\n{repr(response)}") - except Exception: - pass - continue - - # 简单/复杂分别验证与返回 - if scene_mode == "simple": - try: - 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验证成功") - except jsonschema.ValidationError as e: - logging.warning(f"❌ 简单模式验证失败: {e.message}") - continue - # 附加元信息并生成简单可视化(单动作) - plan_id = str(uuid.uuid4()) - pytree_dict['plan_id'] = plan_id - # 简单模式可视化:使用root节点(已经是action类型) - try: - vis_filename = "py_tree.png" - vis_path = os.path.join(self.vis_dir, vis_filename) - # 简单模式的root节点就是action节点,直接使用 - root_node = pytree_dict.get('root', {}) - _visualize_pytree(root_node, os.path.splitext(vis_path)[0]) - pytree_dict['visualization_url'] = f"/static/{vis_filename}" - except Exception as e: - logging.warning(f"简单模式可视化失败: {e}") - - # 保存推理链(若有) - try: - if reasoning_text: - reasoning_path = os.path.join(self.reasoning_dir, "reasoning_content.md") - with open(reasoning_path, 'w', encoding='utf-8') as rf: - rf.write(reasoning_text) - logging.info(f"📝 推理链已保存: {reasoning_path}") - # 终端预览(最多N行) - try: - lines = reasoning_text.splitlines() - preview = "\n".join(lines[: self.reasoning_preview_lines]) - logging.info("🧠 推理链预览(前%d行):\n%s", self.reasoning_preview_lines, preview) - except Exception: - pass - else: - logging.info("未在模型输出中发现 推理链片段。若需捕获,请设置 ENABLE_REASONING_CAPTURE=true 以放宽JSON强制格式。") - except Exception as e: - logging.warning(f"保存推理链Markdown失败: {e}") - # 添加 final_prompt 到返回结果 - pytree_dict['final_prompt'] = final_prompt - - # 保存历史记录 - self._save_history(user_prompt, pytree_dict) - - return pytree_dict - - # 验证生成的复杂行为树 - if _validate_pytree_with_schema(pytree_dict, self.schema): - logging.info("✅ 成功生成并验证了Pytree") - plan_id = str(uuid.uuid4()) - pytree_dict['plan_id'] = plan_id - - # Generate visualization to a static path - vis_filename = "py_tree.png" - vis_path = os.path.join(self.vis_dir, vis_filename) - _visualize_pytree(pytree_dict['root'], os.path.splitext(vis_path)[0]) - pytree_dict['visualization_url'] = f"/static/{vis_filename}" - - # 保存推理链(若有) - try: - if reasoning_text: - reasoning_path = os.path.join(self.reasoning_dir, "reasoning_content.md") - with open(reasoning_path, 'w', encoding='utf-8') as rf: - rf.write(reasoning_text) - logging.info(f"📝 推理链已保存: {reasoning_path}") - # 终端预览(最多N行) - try: - lines = reasoning_text.splitlines() - preview = "\n".join(lines[: self.reasoning_preview_lines]) - logging.info("🧠 推理链预览(前%d行):\n%s", self.reasoning_preview_lines, preview) - except Exception: - pass - else: - logging.info("未在模型输出中发现 推理链片段。若需捕获,请设置 ENABLE_REASONING_CAPTURE=true 以放宽JSON强制格式。") - except Exception as e: - logging.warning(f"保存推理链Markdown失败: {e}") - # 添加 final_prompt 到返回结果 - pytree_dict['final_prompt'] = final_prompt - - # 保存历史记录 - self._save_history(user_prompt, pytree_dict) - - return pytree_dict - else: - # 打印未通过验证的Pytree以便排查 - preview = json.dumps(pytree_dict, ensure_ascii=False, indent=2) - logging.warning(f"❌ 未通过验证的Pytree(第 {attempt + 1}/3 次尝试):\n{preview}") - logging.warning("生成的Pytree验证失败,正在重试...") - except OpenAIError as e: - logging.error(f"生成Pytree时发生错误: {e}") - - raise RuntimeError("在3次尝试后,仍未能生成一个有效的Pytree。") + async def generate(self, user_prompt: str) -> Dict[str, Any]: + logging.info(f"接收到用户请求: {user_prompt}") + return await self.orchestrator.generate(user_prompt) # Create a single instance for the application py_tree_generator = PyTreeGenerator() diff --git a/backend_service/src/retrieval/__init__.py b/backend_service/src/retrieval/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/backend_service/src/retrieval/__init__.py @@ -0,0 +1 @@ + diff --git a/backend_service/src/retrieval/__pycache__/__init__.cpython-313.pyc b/backend_service/src/retrieval/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..be12e3aa Binary files /dev/null and b/backend_service/src/retrieval/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend_service/src/retrieval/__pycache__/retriever.cpython-313.pyc b/backend_service/src/retrieval/__pycache__/retriever.cpython-313.pyc new file mode 100644 index 00000000..9e57bedd Binary files /dev/null and b/backend_service/src/retrieval/__pycache__/retriever.cpython-313.pyc differ diff --git a/backend_service/src/retrieval/adapters/__init__.py b/backend_service/src/retrieval/adapters/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/backend_service/src/retrieval/adapters/__init__.py @@ -0,0 +1 @@ + diff --git a/backend_service/src/retrieval/adapters/__pycache__/__init__.cpython-313.pyc b/backend_service/src/retrieval/adapters/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..a2c927b1 Binary files /dev/null and b/backend_service/src/retrieval/adapters/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend_service/src/retrieval/adapters/__pycache__/chroma_adapter.cpython-313.pyc b/backend_service/src/retrieval/adapters/__pycache__/chroma_adapter.cpython-313.pyc new file mode 100644 index 00000000..20903bf9 Binary files /dev/null and b/backend_service/src/retrieval/adapters/__pycache__/chroma_adapter.cpython-313.pyc differ diff --git a/backend_service/src/retrieval/adapters/chroma_adapter.py b/backend_service/src/retrieval/adapters/chroma_adapter.py new file mode 100644 index 00000000..712c7470 --- /dev/null +++ b/backend_service/src/retrieval/adapters/chroma_adapter.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import logging +from typing import Any, Dict, List, Optional + +import chromadb + + +class ChromaAdapter: + def __init__(self, client: chromadb.PersistentClient): + self.client = client + + def get_collection(self, name: str, embedding_function: Any) -> Optional[Any]: + try: + return self.client.get_collection(name=name, embedding_function=embedding_function) + except Exception as exc: # noqa: BLE001 + logging.warning("集合 %s 不可用: %s", name, exc) + return None + + def query(self, collection: Any, query: str, n_results: int = 3, where: Optional[Dict[str, Any]] = None) -> List[str]: + if collection is None: + return [] + try: + kwargs: Dict[str, Any] = {"query_texts": [query], "n_results": n_results} + if where: + kwargs["where"] = where + results = collection.query(**kwargs) + return results.get("documents", [[]])[0] + except Exception as exc: # noqa: BLE001 + logging.warning("检索失败: %s", exc) + return [] + diff --git a/backend_service/src/retrieval/retriever.py b/backend_service/src/retrieval/retriever.py new file mode 100644 index 00000000..dea19d9d --- /dev/null +++ b/backend_service/src/retrieval/retriever.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from concurrent.futures import ThreadPoolExecutor +from typing import Any, Dict, List, Optional + +from .adapters.chroma_adapter import ChromaAdapter + + +class UnifiedRetriever: + def __init__(self, adapter: ChromaAdapter, embedding_function: Any): + self.adapter = adapter + self.location_collection = adapter.get_collection("location_kb", embedding_function) + self.pattern_collection = adapter.get_collection("pattern_kb", embedding_function) + self.rules_collection = adapter.get_collection("rules_kb", embedding_function) + self.fallback_collection = adapter.get_collection("drone_docs", embedding_function) + + def _query_scope(self, scope: str, query: str, n_results: int = 3) -> List[str]: + if scope == "location": + docs = self.adapter.query(self.location_collection, query, n_results=n_results) + if docs: + return docs + return self.adapter.query(self.fallback_collection, query, n_results=n_results, where={"kb_type": "location"}) + if scope == "pattern": + docs = self.adapter.query(self.pattern_collection, query, n_results=n_results) + if docs: + return docs + return self.adapter.query(self.fallback_collection, query, n_results=n_results, where={"kb_type": "pattern"}) + if scope == "rules": + docs = self.adapter.query(self.rules_collection, query, n_results=n_results) + if docs: + return docs + return self.adapter.query(self.fallback_collection, query, n_results=n_results, where={"kb_type": "rules"}) + return [] + + def retrieve(self, query: str, scopes: List[str], n_results: int = 3) -> Dict[str, Any]: + results: Dict[str, List[str]] = {"location": [], "pattern": [], "rules": []} + with ThreadPoolExecutor(max_workers=3) as executor: + futures = {scope: executor.submit(self._query_scope, scope, query, n_results) for scope in scopes} + for scope, future in futures.items(): + results[scope] = future.result() + return { + "location_context": "\n\n".join(results.get("location", [])), + "pattern_context": "\n\n".join(results.get("pattern", [])), + "rules_context": "\n\n".join(results.get("rules", [])), + "citations": {k: v for k, v in results.items()}, + } + diff --git a/backend_service/src/tools/__pycache__/__init__.cpython-313.pyc b/backend_service/src/tools/__pycache__/__init__.cpython-313.pyc index cf655a69..f75f01da 100644 Binary files a/backend_service/src/tools/__pycache__/__init__.cpython-313.pyc and b/backend_service/src/tools/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend_service/src/tools/__pycache__/coordinate_tools.cpython-313.pyc b/backend_service/src/tools/__pycache__/coordinate_tools.cpython-313.pyc index 1fac4489..0bee9079 100644 Binary files a/backend_service/src/tools/__pycache__/coordinate_tools.cpython-313.pyc and b/backend_service/src/tools/__pycache__/coordinate_tools.cpython-313.pyc differ diff --git a/backend_service/src/validation/__init__.py b/backend_service/src/validation/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/backend_service/src/validation/__init__.py @@ -0,0 +1 @@ + diff --git a/backend_service/src/validation/__pycache__/__init__.cpython-313.pyc b/backend_service/src/validation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..19f9cc21 Binary files /dev/null and b/backend_service/src/validation/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend_service/src/validation/__pycache__/schema_provider.cpython-313.pyc b/backend_service/src/validation/__pycache__/schema_provider.cpython-313.pyc new file mode 100644 index 00000000..d10303ec Binary files /dev/null and b/backend_service/src/validation/__pycache__/schema_provider.cpython-313.pyc differ diff --git a/backend_service/src/validation/__pycache__/validator.cpython-313.pyc b/backend_service/src/validation/__pycache__/validator.cpython-313.pyc new file mode 100644 index 00000000..157c7449 Binary files /dev/null and b/backend_service/src/validation/__pycache__/validator.cpython-313.pyc differ diff --git a/backend_service/src/validation/schema_provider.py b/backend_service/src/validation/schema_provider.py new file mode 100644 index 00000000..4128126a --- /dev/null +++ b/backend_service/src/validation/schema_provider.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from typing import Any, Dict + + +class SchemaProvider: + def __init__(self, complex_schema: Dict[str, Any], simple_schema: Dict[str, Any]): + self.complex_schema = complex_schema + self.simple_schema = simple_schema + diff --git a/backend_service/src/validation/validator.py b/backend_service/src/validation/validator.py new file mode 100644 index 00000000..fcb3dc2d --- /dev/null +++ b/backend_service/src/validation/validator.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import Any, Dict + +import jsonschema + +from .schema_provider import SchemaProvider + + +class PytreeValidator: + def __init__(self, schema_provider: SchemaProvider): + self.schema_provider = schema_provider + + def validate(self, scene_mode: str, payload: Dict[str, Any]) -> None: + if scene_mode == "simple": + jsonschema.validate(instance=payload, schema=self.schema_provider.simple_schema) + root = payload.get("root", {}) + children = root.get("children", []) + if isinstance(children, list) and len(children) > 0: + raise jsonschema.ValidationError("simple模式下 root 不能包含 children") + return + jsonschema.validate(instance=payload, schema=self.schema_provider.complex_schema) + diff --git a/logs/embedding_model.log b/logs/embedding_model.log index a45c6b5f..95b96df2 100644 --- a/logs/embedding_model.log +++ b/logs/embedding_model.log @@ -11,7 +11,7 @@ main: binding port with default address family main: HTTP server is listening, hostname: 0.0.0.0, port: 8090, http threads: 15 main: loading model srv load_model: loading model '/home/huangfukk/models/gguf/Qwen3/Qwen3-Embedding-4B/Qwen3-Embedding-4B-Q5_K_M.gguf' -llama_model_load_from_file_impl: using device CUDA0 (NVIDIA GeForce RTX 4060 Ti) - 2767 MiB free +llama_model_load_from_file_impl: using device CUDA0 (NVIDIA GeForce RTX 4060 Ti) - 15225 MiB free llama_model_loader: loaded meta data with 36 key-value pairs and 398 tensors from /home/huangfukk/models/gguf/Qwen3/Qwen3-Embedding-4B/Qwen3-Embedding-4B-Q5_K_M.gguf (version GGUF V3 (latest)) llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output. llama_model_loader: - kv 0: general.architecture str = qwen3 @@ -120,6 +120,7 @@ print_info: EOG token = 151663 '<|repo_name|>' print_info: EOG token = 151664 '<|file_sep|>' print_info: max token length = 256 load_tensors: loading model tensors, this can take a while... (mmap = true) +srv log_server_r: request: GET /health 127.0.0.1 503 load_tensors: offloading 36 repeating layers to GPU load_tensors: offloaded 36/37 layers to GPU load_tensors: CUDA0 model buffer size = 2445.68 MiB @@ -220,7 +221,227 @@ How are you?<|im_end|> ' main: server is listening on http://0.0.0.0:8090 - starting the main loop srv update_slots: all slots are idle -srv operator(): operator(): cleaning up before exit... -Received second interrupt, terminating immediately. -srv log_server_r: request: GET /health 127.0.0.1 200 -srv operator(): operator(): cleaning up before exit... +slot launch_slot_: id 0 | task 0 | processing task +slot update_slots: id 0 | task 0 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 38 +slot update_slots: id 0 | task 0 | kv cache rm [0, end) +slot update_slots: id 0 | task 0 | prompt processing progress, n_past = 38, n_tokens = 38, progress = 1.000000 +slot update_slots: id 0 | task 0 | prompt done, n_past = 38, n_tokens = 38 +slot release: id 0 | task 0 | stop processing: n_past = 38, truncated = 0 +slot launch_slot_: id 0 | task 1 | processing task +slot update_slots: id 0 | task 1 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 50 +slot update_slots: id 0 | task 1 | kv cache rm [4, end) +slot update_slots: id 0 | task 1 | prompt processing progress, n_past = 50, n_tokens = 46, progress = 0.920000 +slot update_slots: id 0 | task 1 | prompt done, n_past = 50, n_tokens = 46 +slot release: id 0 | task 1 | stop processing: n_past = 50, truncated = 0 +slot launch_slot_: id 0 | task 2 | processing task +slot update_slots: id 0 | task 2 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 142 +slot update_slots: id 0 | task 2 | kv cache rm [4, end) +slot update_slots: id 0 | task 2 | prompt processing progress, n_past = 142, n_tokens = 138, progress = 0.971831 +slot update_slots: id 0 | task 2 | prompt done, n_past = 142, n_tokens = 138 +slot release: id 0 | task 2 | stop processing: n_past = 142, truncated = 0 +slot launch_slot_: id 0 | task 3 | processing task +slot update_slots: id 0 | task 3 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 27 +slot update_slots: id 0 | task 3 | kv cache rm [0, end) +slot update_slots: id 0 | task 3 | prompt processing progress, n_past = 27, n_tokens = 27, progress = 1.000000 +slot update_slots: id 0 | task 3 | prompt done, n_past = 27, n_tokens = 27 +slot release: id 0 | task 3 | stop processing: n_past = 27, truncated = 0 +slot launch_slot_: id 0 | task 4 | processing task +slot update_slots: id 0 | task 4 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 20 +slot update_slots: id 0 | task 4 | kv cache rm [0, end) +slot update_slots: id 0 | task 4 | prompt processing progress, n_past = 20, n_tokens = 20, progress = 1.000000 +slot update_slots: id 0 | task 4 | prompt done, n_past = 20, n_tokens = 20 +slot release: id 0 | task 4 | stop processing: n_past = 20, truncated = 0 +slot launch_slot_: id 0 | task 5 | processing task +slot update_slots: id 0 | task 5 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 17 +slot update_slots: id 0 | task 5 | kv cache rm [0, end) +slot update_slots: id 0 | task 5 | prompt processing progress, n_past = 17, n_tokens = 17, progress = 1.000000 +slot update_slots: id 0 | task 5 | prompt done, n_past = 17, n_tokens = 17 +slot release: id 0 | task 5 | stop processing: n_past = 17, truncated = 0 +slot launch_slot_: id 0 | task 6 | processing task +slot update_slots: id 0 | task 6 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 10 +slot update_slots: id 0 | task 6 | kv cache rm [0, end) +slot update_slots: id 0 | task 6 | prompt processing progress, n_past = 10, n_tokens = 10, progress = 1.000000 +slot update_slots: id 0 | task 6 | prompt done, n_past = 10, n_tokens = 10 +slot release: id 0 | task 6 | stop processing: n_past = 10, truncated = 0 +slot launch_slot_: id 0 | task 7 | processing task +slot update_slots: id 0 | task 7 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 17 +slot update_slots: id 0 | task 7 | kv cache rm [0, end) +slot update_slots: id 0 | task 7 | prompt processing progress, n_past = 17, n_tokens = 17, progress = 1.000000 +slot update_slots: id 0 | task 7 | prompt done, n_past = 17, n_tokens = 17 +slot release: id 0 | task 7 | stop processing: n_past = 17, truncated = 0 +slot launch_slot_: id 0 | task 8 | processing task +slot update_slots: id 0 | task 8 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 18 +slot update_slots: id 0 | task 8 | kv cache rm [0, end) +slot update_slots: id 0 | task 8 | prompt processing progress, n_past = 18, n_tokens = 18, progress = 1.000000 +slot update_slots: id 0 | task 8 | prompt done, n_past = 18, n_tokens = 18 +slot release: id 0 | task 8 | stop processing: n_past = 18, truncated = 0 +slot launch_slot_: id 0 | task 9 | processing task +slot update_slots: id 0 | task 9 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 23 +slot update_slots: id 0 | task 9 | kv cache rm [0, end) +slot update_slots: id 0 | task 9 | prompt processing progress, n_past = 23, n_tokens = 23, progress = 1.000000 +slot update_slots: id 0 | task 9 | prompt done, n_past = 23, n_tokens = 23 +slot release: id 0 | task 9 | stop processing: n_past = 23, truncated = 0 +slot launch_slot_: id 0 | task 10 | processing task +slot update_slots: id 0 | task 10 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 25 +slot update_slots: id 0 | task 10 | kv cache rm [0, end) +slot update_slots: id 0 | task 10 | prompt processing progress, n_past = 25, n_tokens = 25, progress = 1.000000 +slot update_slots: id 0 | task 10 | prompt done, n_past = 25, n_tokens = 25 +slot release: id 0 | task 10 | stop processing: n_past = 25, truncated = 0 +slot launch_slot_: id 0 | task 11 | processing task +slot update_slots: id 0 | task 11 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 24 +slot update_slots: id 0 | task 11 | kv cache rm [0, end) +slot update_slots: id 0 | task 11 | prompt processing progress, n_past = 24, n_tokens = 24, progress = 1.000000 +slot update_slots: id 0 | task 11 | prompt done, n_past = 24, n_tokens = 24 +slot release: id 0 | task 11 | stop processing: n_past = 24, truncated = 0 +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot launch_slot_: id 0 | task 24 | processing task +slot update_slots: id 0 | task 24 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 38 +slot update_slots: id 0 | task 24 | kv cache rm [0, end) +slot update_slots: id 0 | task 24 | prompt processing progress, n_past = 38, n_tokens = 38, progress = 1.000000 +slot update_slots: id 0 | task 24 | prompt done, n_past = 38, n_tokens = 38 +slot release: id 0 | task 24 | stop processing: n_past = 38, truncated = 0 +slot launch_slot_: id 0 | task 25 | processing task +slot update_slots: id 0 | task 25 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 50 +slot update_slots: id 0 | task 25 | kv cache rm [4, end) +slot update_slots: id 0 | task 25 | prompt processing progress, n_past = 50, n_tokens = 46, progress = 0.920000 +slot update_slots: id 0 | task 25 | prompt done, n_past = 50, n_tokens = 46 +slot release: id 0 | task 25 | stop processing: n_past = 50, truncated = 0 +slot launch_slot_: id 0 | task 26 | processing task +slot update_slots: id 0 | task 26 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 142 +slot update_slots: id 0 | task 26 | kv cache rm [4, end) +slot update_slots: id 0 | task 26 | prompt processing progress, n_past = 142, n_tokens = 138, progress = 0.971831 +slot update_slots: id 0 | task 26 | prompt done, n_past = 142, n_tokens = 138 +slot release: id 0 | task 26 | stop processing: n_past = 142, truncated = 0 +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot launch_slot_: id 0 | task 30 | processing task +slot update_slots: id 0 | task 30 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 27 +slot update_slots: id 0 | task 30 | kv cache rm [0, end) +slot update_slots: id 0 | task 30 | prompt processing progress, n_past = 27, n_tokens = 27, progress = 1.000000 +slot update_slots: id 0 | task 30 | prompt done, n_past = 27, n_tokens = 27 +slot release: id 0 | task 30 | stop processing: n_past = 27, truncated = 0 +slot launch_slot_: id 0 | task 31 | processing task +slot update_slots: id 0 | task 31 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 20 +slot update_slots: id 0 | task 31 | kv cache rm [0, end) +slot update_slots: id 0 | task 31 | prompt processing progress, n_past = 20, n_tokens = 20, progress = 1.000000 +slot update_slots: id 0 | task 31 | prompt done, n_past = 20, n_tokens = 20 +slot release: id 0 | task 31 | stop processing: n_past = 20, truncated = 0 +slot launch_slot_: id 0 | task 32 | processing task +slot update_slots: id 0 | task 32 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 17 +slot update_slots: id 0 | task 32 | kv cache rm [0, end) +slot update_slots: id 0 | task 32 | prompt processing progress, n_past = 17, n_tokens = 17, progress = 1.000000 +slot update_slots: id 0 | task 32 | prompt done, n_past = 17, n_tokens = 17 +slot release: id 0 | task 32 | stop processing: n_past = 17, truncated = 0 +slot launch_slot_: id 0 | task 33 | processing task +slot update_slots: id 0 | task 33 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 10 +slot update_slots: id 0 | task 33 | kv cache rm [0, end) +slot update_slots: id 0 | task 33 | prompt processing progress, n_past = 10, n_tokens = 10, progress = 1.000000 +slot update_slots: id 0 | task 33 | prompt done, n_past = 10, n_tokens = 10 +slot release: id 0 | task 33 | stop processing: n_past = 10, truncated = 0 +slot launch_slot_: id 0 | task 34 | processing task +slot update_slots: id 0 | task 34 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 17 +slot update_slots: id 0 | task 34 | kv cache rm [0, end) +slot update_slots: id 0 | task 34 | prompt processing progress, n_past = 17, n_tokens = 17, progress = 1.000000 +slot update_slots: id 0 | task 34 | prompt done, n_past = 17, n_tokens = 17 +slot release: id 0 | task 34 | stop processing: n_past = 17, truncated = 0 +slot launch_slot_: id 0 | task 35 | processing task +slot update_slots: id 0 | task 35 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 18 +slot update_slots: id 0 | task 35 | kv cache rm [0, end) +slot update_slots: id 0 | task 35 | prompt processing progress, n_past = 18, n_tokens = 18, progress = 1.000000 +slot update_slots: id 0 | task 35 | prompt done, n_past = 18, n_tokens = 18 +slot release: id 0 | task 35 | stop processing: n_past = 18, truncated = 0 +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot launch_slot_: id 0 | task 42 | processing task +slot update_slots: id 0 | task 42 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 23 +slot update_slots: id 0 | task 42 | kv cache rm [0, end) +slot update_slots: id 0 | task 42 | prompt processing progress, n_past = 23, n_tokens = 23, progress = 1.000000 +slot update_slots: id 0 | task 42 | prompt done, n_past = 23, n_tokens = 23 +slot release: id 0 | task 42 | stop processing: n_past = 23, truncated = 0 +slot launch_slot_: id 0 | task 43 | processing task +slot update_slots: id 0 | task 43 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 25 +slot update_slots: id 0 | task 43 | kv cache rm [0, end) +slot update_slots: id 0 | task 43 | prompt processing progress, n_past = 25, n_tokens = 25, progress = 1.000000 +slot update_slots: id 0 | task 43 | prompt done, n_past = 25, n_tokens = 25 +slot release: id 0 | task 43 | stop processing: n_past = 25, truncated = 0 +slot launch_slot_: id 0 | task 44 | processing task +slot update_slots: id 0 | task 44 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 24 +slot update_slots: id 0 | task 44 | kv cache rm [0, end) +slot update_slots: id 0 | task 44 | prompt processing progress, n_past = 24, n_tokens = 24, progress = 1.000000 +slot update_slots: id 0 | task 44 | prompt done, n_past = 24, n_tokens = 24 +slot release: id 0 | task 44 | stop processing: n_past = 24, truncated = 0 +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot launch_slot_: id 0 | task 48 | processing task +slot update_slots: id 0 | task 48 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 2 +slot update_slots: id 0 | task 48 | kv cache rm [0, end) +slot update_slots: id 0 | task 48 | prompt processing progress, n_past = 2, n_tokens = 2, progress = 1.000000 +slot update_slots: id 0 | task 48 | prompt done, n_past = 2, n_tokens = 2 +slot release: id 0 | task 48 | stop processing: n_past = 2, truncated = 0 +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot launch_slot_: id 0 | task 50 | processing task +slot update_slots: id 0 | task 50 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 12 +slot update_slots: id 0 | task 50 | kv cache rm [0, end) +slot update_slots: id 0 | task 50 | prompt processing progress, n_past = 12, n_tokens = 12, progress = 1.000000 +slot update_slots: id 0 | task 50 | prompt done, n_past = 12, n_tokens = 12 +slot release: id 0 | task 50 | stop processing: n_past = 12, truncated = 0 +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot launch_slot_: id 0 | task 52 | processing task +slot update_slots: id 0 | task 52 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 17 +slot update_slots: id 0 | task 52 | kv cache rm [3, end) +slot update_slots: id 0 | task 52 | prompt processing progress, n_past = 17, n_tokens = 14, progress = 0.823529 +slot update_slots: id 0 | task 52 | prompt done, n_past = 17, n_tokens = 14 +slot release: id 0 | task 52 | stop processing: n_past = 17, truncated = 0 +slot launch_slot_: id 0 | task 53 | processing task +slot update_slots: id 0 | task 53 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 17 +slot update_slots: id 0 | task 53 | need to evaluate at least 1 token for each active slot, n_past = 17, n_prompt_tokens = 17 +slot update_slots: id 0 | task 53 | kv cache rm [16, end) +slot update_slots: id 0 | task 53 | prompt processing progress, n_past = 17, n_tokens = 1, progress = 0.058824 +slot update_slots: id 0 | task 53 | prompt done, n_past = 17, n_tokens = 1 +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot release: id 0 | task 53 | stop processing: n_past = 17, truncated = 0 +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot launch_slot_: id 0 | task 56 | processing task +slot update_slots: id 0 | task 56 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 16 +slot update_slots: id 0 | task 56 | kv cache rm [5, end) +slot update_slots: id 0 | task 56 | prompt processing progress, n_past = 16, n_tokens = 11, progress = 0.687500 +slot update_slots: id 0 | task 56 | prompt done, n_past = 16, n_tokens = 11 +slot release: id 0 | task 56 | stop processing: n_past = 16, truncated = 0 +slot launch_slot_: id 0 | task 59 | processing task +slot update_slots: id 0 | task 59 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 16 +slot update_slots: id 0 | task 59 | need to evaluate at least 1 token for each active slot, n_past = 16, n_prompt_tokens = 16 +slot update_slots: id 0 | task 59 | kv cache rm [15, end) +slot update_slots: id 0 | task 59 | prompt processing progress, n_past = 16, n_tokens = 1, progress = 0.062500 +slot update_slots: id 0 | task 59 | prompt done, n_past = 16, n_tokens = 1 +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot release: id 0 | task 59 | stop processing: n_past = 16, truncated = 0 +slot launch_slot_: id 0 | task 57 | processing task +slot update_slots: id 0 | task 57 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 16 +slot update_slots: id 0 | task 57 | need to evaluate at least 1 token for each active slot, n_past = 16, n_prompt_tokens = 16 +slot update_slots: id 0 | task 57 | kv cache rm [15, end) +slot update_slots: id 0 | task 57 | prompt processing progress, n_past = 16, n_tokens = 1, progress = 0.062500 +slot update_slots: id 0 | task 57 | prompt done, n_past = 16, n_tokens = 1 +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot release: id 0 | task 57 | stop processing: n_past = 16, truncated = 0 +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot launch_slot_: id 0 | task 62 | processing task +slot update_slots: id 0 | task 62 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 15 +slot update_slots: id 0 | task 62 | kv cache rm [5, end) +slot update_slots: id 0 | task 62 | prompt processing progress, n_past = 15, n_tokens = 10, progress = 0.666667 +slot update_slots: id 0 | task 62 | prompt done, n_past = 15, n_tokens = 10 +slot release: id 0 | task 62 | stop processing: n_past = 15, truncated = 0 +slot launch_slot_: id 0 | task 64 | processing task +slot update_slots: id 0 | task 64 | new prompt, n_ctx_slot = 4096, n_keep = 0, n_prompt_tokens = 15 +slot update_slots: id 0 | task 64 | need to evaluate at least 1 token for each active slot, n_past = 15, n_prompt_tokens = 15 +slot update_slots: id 0 | task 64 | kv cache rm [14, end) +slot update_slots: id 0 | task 64 | prompt processing progress, n_past = 15, n_tokens = 1, progress = 0.066667 +slot update_slots: id 0 | task 64 | prompt done, n_past = 15, n_tokens = 1 +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 +slot release: id 0 | task 64 | stop processing: n_past = 15, truncated = 0 +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/embeddings 127.0.0.1 200 diff --git a/logs/fastapi.log b/logs/fastapi.log index 094a39c3..a0ec3459 100644 --- a/logs/fastapi.log +++ b/logs/fastapi.log @@ -1,15 +1,47 @@ -2026-01-20 09:41:43,111 - INFO - Anonymized telemetry enabled. See https://docs.trychroma.com/telemetry for more information. -2026-01-20 09:41:43,260 - INFO - 成功找到节点定义JSON代码块 -2026-01-20 09:41:43,260 - INFO - 成功解析出动作节点: ['approach_target', 'deliver_payload', 'fly_sequence', 'fly_to_waypoint', 'land', 'loiter', 'manual_confirmation', 'move_direction', 'object_detect', 'return_emergency', 'rotate', 'rotate_search', 'search_pattern', 'system_checks', 'take_photos', 'takeoff', 'track_object'] -2026-01-20 09:41:43,260 - INFO - 成功解析出条件节点: ['at_waypoint', 'object_detected'] -INFO: Started server process [34239] +2026-02-20 22:25:24,889 - ERROR - 提示词文件未找到 -> classifier_prompt.txt +2026-02-20 22:25:24,979 - INFO - Anonymized telemetry enabled. See https://docs.trychroma.com/telemetry for more information. +2026-02-20 22:25:25,151 - INFO - 成功找到节点定义JSON代码块 +2026-02-20 22:25:25,152 - INFO - 成功解析出动作节点: ['approach_target', 'deliver_payload', 'fly_sequence', 'fly_to_waypoint', 'land', 'loiter', 'manual_confirmation', 'move_direction', 'object_detect', 'return_emergency', 'rotate', 'rotate_search', 'search_pattern', 'system_checks', 'take_photos', 'takeoff', 'track_object'] +2026-02-20 22:25:25,152 - INFO - 成功解析出条件节点: ['at_waypoint', 'object_detected'] +2026-02-20 22:25:25,152 - WARNING - 集合 location_kb 不可用: Collection [location_kb] does not exist +2026-02-20 22:25:25,152 - WARNING - 集合 pattern_kb 不可用: Collection [pattern_kb] does not exist +2026-02-20 22:25:25,153 - WARNING - 集合 rules_kb 不可用: Collection [rules_kb] does not exist +INFO: Started server process [10575] INFO: Waiting for application startup. -2026-01-20 09:41:43,263 - INFO - WebSocket event loop configured. +2026-02-20 22:25:25,155 - INFO - WebSocket event loop configured. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) -INFO: 127.0.0.1:44013 - "GET /docs HTTP/1.1" 200 OK -INFO: Shutting down -INFO: Waiting for application shutdown. -2026-01-20 09:42:11,885 - INFO - Backend service shutting down. -INFO: Application shutdown complete. -INFO: Finished server process [34239] +INFO: 127.0.0.1:45093 - "GET /docs HTTP/1.1" 200 OK +INFO: 127.0.0.1:46985 - "GET /docs HTTP/1.1" 200 OK +2026-02-20 22:29:37,482 - INFO - 接收到用户请求: 无人机当前在空中,往北飞50米 +2026-02-20 22:29:38,939 - INFO - HTTP Request: POST http://localhost:8081/v1/chat/completions "HTTP/1.1 200 OK" +2026-02-20 22:29:40,302 - INFO - HTTP Request: POST http://localhost:8081/v1/chat/completions "HTTP/1.1 200 OK" +2026-02-20 22:29:42,389 - INFO - ✅ 任务树可视化成功 +2026-02-20 22:29:42,389 - INFO - 图形已保存到: /home/huangfukk/DronePlanning/backend_service/generated_visualizations/py_tree.png +2026-02-20 22:29:42,389 - INFO - 💾 历史记录已保存: /home/huangfukk/DronePlanning/backend_service/src/../history/20260220_222942_plan.json +2026-02-20 22:29:42,389 - INFO - ✅ 成功生成并验证了Pytree(Pipeline) +INFO: 127.0.0.1:44861 - "POST /generate_plan HTTP/1.1" 200 OK +2026-02-20 22:30:15,774 - INFO - 接收到用户请求: 无人机当前在地面,到广场查找穿红色衣服的人,找到后拍照 +2026-02-20 22:30:16,805 - INFO - HTTP Request: POST http://localhost:8081/v1/chat/completions "HTTP/1.1 200 OK" +2026-02-20 22:30:29,109 - INFO - HTTP Request: POST http://localhost:8081/v1/chat/completions "HTTP/1.1 200 OK" +2026-02-20 22:30:29,161 - INFO - ✅ 任务树可视化成功 +2026-02-20 22:30:29,161 - INFO - 图形已保存到: /home/huangfukk/DronePlanning/backend_service/generated_visualizations/py_tree.png +2026-02-20 22:30:29,161 - INFO - 💾 历史记录已保存: /home/huangfukk/DronePlanning/backend_service/src/../history/20260220_223029_plan.json +2026-02-20 22:30:29,161 - INFO - ✅ 成功生成并验证了Pytree(Pipeline) +INFO: 127.0.0.1:43927 - "POST /generate_plan HTTP/1.1" 200 OK +2026-02-20 22:32:13,176 - INFO - 接收到用户请求: 无人机当前在地面,去面前大楼左边20米巡查并拍照 +2026-02-20 22:32:14,331 - INFO - HTTP Request: POST http://localhost:8081/v1/chat/completions "HTTP/1.1 200 OK" +2026-02-20 22:32:30,714 - INFO - HTTP Request: POST http://localhost:8081/v1/chat/completions "HTTP/1.1 200 OK" +2026-02-20 22:32:30,764 - INFO - ✅ 任务树可视化成功 +2026-02-20 22:32:30,764 - INFO - 图形已保存到: /home/huangfukk/DronePlanning/backend_service/generated_visualizations/py_tree.png +2026-02-20 22:32:30,765 - INFO - 💾 历史记录已保存: /home/huangfukk/DronePlanning/backend_service/src/../history/20260220_223230_plan.json +2026-02-20 22:32:30,765 - INFO - ✅ 成功生成并验证了Pytree(Pipeline) +INFO: 127.0.0.1:45399 - "POST /generate_plan HTTP/1.1" 200 OK +2026-02-20 22:36:36,485 - INFO - 接收到用户请求: 无人机当前在地面,到广场查找绿色公交车,找到就拍照 +2026-02-20 22:36:37,548 - INFO - HTTP Request: POST http://localhost:8081/v1/chat/completions "HTTP/1.1 200 OK" +2026-02-20 22:36:48,200 - INFO - HTTP Request: POST http://localhost:8081/v1/chat/completions "HTTP/1.1 200 OK" +2026-02-20 22:36:48,248 - INFO - ✅ 任务树可视化成功 +2026-02-20 22:36:48,248 - INFO - 图形已保存到: /home/huangfukk/DronePlanning/backend_service/generated_visualizations/py_tree.png +2026-02-20 22:36:48,248 - INFO - 💾 历史记录已保存: /home/huangfukk/DronePlanning/backend_service/src/../history/20260220_223648_plan.json +2026-02-20 22:36:48,248 - INFO - ✅ 成功生成并验证了Pytree(Pipeline) +INFO: 127.0.0.1:47209 - "POST /generate_plan HTTP/1.1" 200 OK diff --git a/logs/inference_model.log b/logs/inference_model.log index e6cb2438..ae59b0b3 100644 --- a/logs/inference_model.log +++ b/logs/inference_model.log @@ -11,7 +11,7 @@ main: binding port with default address family main: HTTP server is listening, hostname: 0.0.0.0, port: 8081, http threads: 15 main: loading model srv load_model: loading model '/home/huangfukk/models/gguf/Qwen3/Qwen3-4B/Qwen3-4B-Q5_K_M.gguf' -llama_model_load_from_file_impl: using device CUDA0 (NVIDIA GeForce RTX 4060 Ti) - 2789 MiB free +llama_model_load_from_file_impl: using device CUDA0 (NVIDIA GeForce RTX 4060 Ti) - 15225 MiB free llama_model_loader: loaded meta data with 28 key-value pairs and 398 tensors from /home/huangfukk/models/gguf/Qwen3/Qwen3-4B/Qwen3-4B-Q5_K_M.gguf (version GGUF V3 (latest)) llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output. llama_model_loader: - kv 0: general.architecture str = qwen3 @@ -112,6 +112,7 @@ print_info: EOG token = 151663 '<|repo_name|>' print_info: EOG token = 151664 '<|file_sep|>' print_info: max token length = 256 load_tensors: loading model tensors, this can take a while... (mmap = true) +srv log_server_r: request: GET /health 127.0.0.1 503 load_tensors: offloading 36 repeating layers to GPU load_tensors: offloaded 36/37 layers to GPU load_tensors: CUDA0 model buffer size = 2445.68 MiB @@ -119,8 +120,8 @@ load_tensors: CPU_Mapped model buffer size = 304.29 MiB .......................................................................................... llama_context: constructing llama_context llama_context: n_seq_max = 1 -llama_context: n_ctx = 8192 -llama_context: n_ctx_per_seq = 8192 +llama_context: n_ctx = 16384 +llama_context: n_ctx_per_seq = 16384 llama_context: n_batch = 2048 llama_context: n_ubatch = 512 llama_context: causal_attn = 1 @@ -128,12 +129,12 @@ llama_context: flash_attn = 0 llama_context: kv_unified = false llama_context: freq_base = 1000000.0 llama_context: freq_scale = 1 -llama_context: n_ctx_per_seq (8192) < n_ctx_train (40960) -- the full capacity of the model will not be utilized +llama_context: n_ctx_per_seq (16384) < n_ctx_train (40960) -- the full capacity of the model will not be utilized llama_context: CPU output buffer size = 0.58 MiB -llama_kv_cache_unified: CUDA0 KV buffer size = 1152.00 MiB -llama_kv_cache_unified: size = 1152.00 MiB ( 8192 cells, 36 layers, 1/1 seqs), K (f16): 576.00 MiB, V (f16): 576.00 MiB -llama_context: CUDA0 compute buffer size = 606.03 MiB -llama_context: CUDA_Host compute buffer size = 25.01 MiB +llama_kv_cache_unified: CUDA0 KV buffer size = 2304.00 MiB +llama_kv_cache_unified: size = 2304.00 MiB ( 16384 cells, 36 layers, 1/1 seqs), K (f16): 1152.00 MiB, V (f16): 1152.00 MiB +llama_context: CUDA0 compute buffer size = 1086.01 MiB +llama_context: CUDA_Host compute buffer size = 41.01 MiB llama_context: graph nodes = 1410 llama_context: graph splits = 4 (with bs=512), 3 (with bs=1) common_init_from_params: added <|endoftext|> logit bias = -inf @@ -141,10 +142,10 @@ common_init_from_params: added <|im_end|> logit bias = -inf common_init_from_params: added <|fim_pad|> logit bias = -inf common_init_from_params: added <|repo_name|> logit bias = -inf common_init_from_params: added <|file_sep|> logit bias = -inf -common_init_from_params: setting dry_penalty_last_n to ctx_size = 8192 +common_init_from_params: setting dry_penalty_last_n to ctx_size = 16384 common_init_from_params: warming up the model with an empty run - please wait ... (--no-warmup to disable) srv init: initializing slots, n_slots = 1 -slot init: id 0 | task -1 | new slot n_ctx_slot = 8192 +slot init: id 0 | task -1 | new slot n_ctx_slot = 16384 main: model loaded main: chat template, chat_template: {%- if tools %} {{- '<|im_start|>system\n' }} @@ -242,8 +243,122 @@ How are you?<|im_end|> ' main: server is listening on http://0.0.0.0:8081 - starting the main loop srv update_slots: all slots are idle -srv operator(): operator(): cleaning up before exit... -ed second interrupt, terminating immediately. -srv operator(): operator(): cleaning up before exit... -srv log_server_r: request: GET /health 127.0.0.1 200 -srv operator(): operator(): cleaning up before exit... +srv log_server_r: request: GET /v1/models 127.0.0.1 200 +srv params_from_: Chat format: Content-only +slot launch_slot_: id 0 | task 0 | processing task +slot update_slots: id 0 | task 0 | new prompt, n_ctx_slot = 16384, n_keep = 0, n_prompt_tokens = 1569 +slot update_slots: id 0 | task 0 | kv cache rm [0, end) +slot update_slots: id 0 | task 0 | prompt processing progress, n_past = 1569, n_tokens = 1569, progress = 1.000000 +slot update_slots: id 0 | task 0 | prompt done, n_past = 1569, n_tokens = 1569 +slot release: id 0 | task 0 | stop processing: n_past = 1578, truncated = 0 +slot print_timing: id 0 | task 0 | +prompt eval time = 1012.37 ms / 1569 tokens ( 0.65 ms per token, 1549.82 tokens per second) + eval time = 258.11 ms / 10 tokens ( 25.81 ms per token, 38.74 tokens per second) + total time = 1270.48 ms / 1579 tokens +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/chat/completions 127.0.0.1 200 +srv params_from_: Chat format: Content-only +slot launch_slot_: id 0 | task 11 | processing task +slot update_slots: id 0 | task 11 | new prompt, n_ctx_slot = 16384, n_keep = 0, n_prompt_tokens = 1611 +slot update_slots: id 0 | task 11 | kv cache rm [5, end) +slot update_slots: id 0 | task 11 | prompt processing progress, n_past = 1611, n_tokens = 1606, progress = 0.996896 +slot update_slots: id 0 | task 11 | prompt done, n_past = 1611, n_tokens = 1606 +slot release: id 0 | task 11 | stop processing: n_past = 1636, truncated = 0 +slot print_timing: id 0 | task 11 | +prompt eval time = 613.51 ms / 1606 tokens ( 0.38 ms per token, 2617.71 tokens per second) + eval time = 653.35 ms / 26 tokens ( 25.13 ms per token, 39.80 tokens per second) + total time = 1266.86 ms / 1632 tokens +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/chat/completions 127.0.0.1 200 +srv params_from_: Chat format: Content-only +slot launch_slot_: id 0 | task 38 | processing task +slot update_slots: id 0 | task 38 | new prompt, n_ctx_slot = 16384, n_keep = 0, n_prompt_tokens = 1574 +slot update_slots: id 0 | task 38 | kv cache rm [5, end) +slot update_slots: id 0 | task 38 | prompt processing progress, n_past = 1574, n_tokens = 1569, progress = 0.996823 +slot update_slots: id 0 | task 38 | prompt done, n_past = 1574, n_tokens = 1569 +slot release: id 0 | task 38 | stop processing: n_past = 1584, truncated = 0 +slot print_timing: id 0 | task 38 | +prompt eval time = 768.98 ms / 1569 tokens ( 0.49 ms per token, 2040.36 tokens per second) + eval time = 254.75 ms / 11 tokens ( 23.16 ms per token, 43.18 tokens per second) + total time = 1023.74 ms / 1580 tokens +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/chat/completions 127.0.0.1 200 +srv params_from_: Chat format: Content-only +slot launch_slot_: id 0 | task 50 | processing task +slot update_slots: id 0 | task 50 | new prompt, n_ctx_slot = 16384, n_keep = 0, n_prompt_tokens = 4929 +slot update_slots: id 0 | task 50 | kv cache rm [3, end) +slot update_slots: id 0 | task 50 | prompt processing progress, n_past = 2051, n_tokens = 2048, progress = 0.415500 +slot update_slots: id 0 | task 50 | kv cache rm [2051, end) +slot update_slots: id 0 | task 50 | prompt processing progress, n_past = 4099, n_tokens = 2048, progress = 0.831000 +slot update_slots: id 0 | task 50 | kv cache rm [4099, end) +slot update_slots: id 0 | task 50 | prompt processing progress, n_past = 4929, n_tokens = 830, progress = 0.999391 +slot update_slots: id 0 | task 50 | prompt done, n_past = 4929, n_tokens = 830 +slot release: id 0 | task 50 | stop processing: n_past = 5254, truncated = 0 +slot print_timing: id 0 | task 50 | +prompt eval time = 2682.34 ms / 4926 tokens ( 0.54 ms per token, 1836.46 tokens per second) + eval time = 9502.10 ms / 326 tokens ( 29.15 ms per token, 34.31 tokens per second) + total time = 12184.44 ms / 5252 tokens +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/chat/completions 127.0.0.1 200 +srv params_from_: Chat format: Content-only +slot launch_slot_: id 0 | task 379 | processing task +slot update_slots: id 0 | task 379 | new prompt, n_ctx_slot = 16384, n_keep = 0, n_prompt_tokens = 1573 +slot update_slots: id 0 | task 379 | kv cache rm [3, end) +slot update_slots: id 0 | task 379 | prompt processing progress, n_past = 1573, n_tokens = 1570, progress = 0.998093 +slot update_slots: id 0 | task 379 | prompt done, n_past = 1573, n_tokens = 1570 +slot release: id 0 | task 379 | stop processing: n_past = 1583, truncated = 0 +slot print_timing: id 0 | task 379 | +prompt eval time = 862.21 ms / 1570 tokens ( 0.55 ms per token, 1820.91 tokens per second) + eval time = 284.82 ms / 11 tokens ( 25.89 ms per token, 38.62 tokens per second) + total time = 1147.03 ms / 1581 tokens +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/chat/completions 127.0.0.1 200 +srv params_from_: Chat format: Content-only +slot launch_slot_: id 0 | task 391 | processing task +slot update_slots: id 0 | task 391 | new prompt, n_ctx_slot = 16384, n_keep = 0, n_prompt_tokens = 7208 +slot update_slots: id 0 | task 391 | kv cache rm [3, end) +slot update_slots: id 0 | task 391 | prompt processing progress, n_past = 2051, n_tokens = 2048, progress = 0.284129 +slot update_slots: id 0 | task 391 | kv cache rm [2051, end) +slot update_slots: id 0 | task 391 | prompt processing progress, n_past = 4099, n_tokens = 2048, progress = 0.568258 +slot update_slots: id 0 | task 391 | kv cache rm [4099, end) +slot update_slots: id 0 | task 391 | prompt processing progress, n_past = 6147, n_tokens = 2048, progress = 0.852386 +slot update_slots: id 0 | task 391 | kv cache rm [6147, end) +slot update_slots: id 0 | task 391 | prompt processing progress, n_past = 7208, n_tokens = 1061, progress = 0.999584 +slot update_slots: id 0 | task 391 | prompt done, n_past = 7208, n_tokens = 1061 +slot release: id 0 | task 391 | stop processing: n_past = 7577, truncated = 0 +slot print_timing: id 0 | task 391 | +prompt eval time = 4810.49 ms / 7205 tokens ( 0.67 ms per token, 1497.77 tokens per second) + eval time = 11443.72 ms / 370 tokens ( 30.93 ms per token, 32.33 tokens per second) + total time = 16254.21 ms / 7575 tokens +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/chat/completions 127.0.0.1 200 +srv params_from_: Chat format: Content-only +slot launch_slot_: id 0 | task 765 | processing task +slot update_slots: id 0 | task 765 | new prompt, n_ctx_slot = 16384, n_keep = 0, n_prompt_tokens = 1572 +slot update_slots: id 0 | task 765 | kv cache rm [3, end) +slot update_slots: id 0 | task 765 | prompt processing progress, n_past = 1572, n_tokens = 1569, progress = 0.998092 +slot update_slots: id 0 | task 765 | prompt done, n_past = 1572, n_tokens = 1569 +slot release: id 0 | task 765 | stop processing: n_past = 1582, truncated = 0 +slot print_timing: id 0 | task 765 | +prompt eval time = 826.51 ms / 1569 tokens ( 0.53 ms per token, 1898.34 tokens per second) + eval time = 227.62 ms / 11 tokens ( 20.69 ms per token, 48.33 tokens per second) + total time = 1054.13 ms / 1580 tokens +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/chat/completions 127.0.0.1 200 +srv params_from_: Chat format: Content-only +slot launch_slot_: id 0 | task 777 | processing task +slot update_slots: id 0 | task 777 | new prompt, n_ctx_slot = 16384, n_keep = 0, n_prompt_tokens = 4927 +slot update_slots: id 0 | task 777 | kv cache rm [3, end) +slot update_slots: id 0 | task 777 | prompt processing progress, n_past = 2051, n_tokens = 2048, progress = 0.415669 +slot update_slots: id 0 | task 777 | kv cache rm [2051, end) +slot update_slots: id 0 | task 777 | prompt processing progress, n_past = 4099, n_tokens = 2048, progress = 0.831338 +slot update_slots: id 0 | task 777 | kv cache rm [4099, end) +slot update_slots: id 0 | task 777 | prompt processing progress, n_past = 4927, n_tokens = 828, progress = 0.999391 +slot update_slots: id 0 | task 777 | prompt done, n_past = 4927, n_tokens = 828 +slot release: id 0 | task 777 | stop processing: n_past = 5246, truncated = 0 +slot print_timing: id 0 | task 777 | +prompt eval time = 2382.24 ms / 4924 tokens ( 0.48 ms per token, 2066.96 tokens per second) + eval time = 8172.57 ms / 320 tokens ( 25.54 ms per token, 39.16 tokens per second) + total time = 10554.82 ms / 5244 tokens +srv update_slots: all slots are idle +srv log_server_r: request: POST /v1/chat/completions 127.0.0.1 200 diff --git a/logs/services.pid b/logs/services.pid new file mode 100644 index 00000000..9966e059 --- /dev/null +++ b/logs/services.pid @@ -0,0 +1,3 @@ +10395 +10396 +10573 diff --git a/logs/services_vllm.pid b/logs/services_vllm.pid new file mode 100644 index 00000000..0a3fd610 --- /dev/null +++ b/logs/services_vllm.pid @@ -0,0 +1,2 @@ +43538 +43539 diff --git a/logs/vllm_inference_model.log b/logs/vllm_inference_model.log new file mode 100644 index 00000000..fe3a57c3 --- /dev/null +++ b/logs/vllm_inference_model.log @@ -0,0 +1,150 @@ +[DEBUG] conda_env=VLLM +[DEBUG] CONDA_PREFIX=/home/huangfukk/miniconda3/envs/VLLM +[DEBUG] which python: /home/huangfukk/miniconda3/envs/VLLM/bin/python +[DEBUG] which vllm: /home/huangfukk/miniconda3/envs/VLLM/bin/vllm +0.13.0 +[DEBUG] python: 3.12.12 | packaged by Anaconda, Inc. | (main, Oct 21 2025, 20:16:04) [GCC 11.2.0] +[DEBUG] torch: 2.9.0+cu128 cuda: 12.8 available: True +(APIServer pid=43538) INFO 01-20 16:25:05 [api_server.py:1351] vLLM API server version 0.13.0 +(APIServer pid=43538) INFO 01-20 16:25:05 [utils.py:253] non-default args: {'model_tag': '/home/huangfukk/models/AWQ/Qwen3-4B-Instruct-2507', 'host': '0.0.0.0', 'port': 8081, 'uvicorn_log_level': 'debug', 'chat_template': '/home/huangfukk/DronePlanning/tools/vllm_templates/qwen3_xml_tool.jinja', 'chat_template_content_format': 'string', 'enable_auto_tool_choice': True, 'tool_call_parser': 'qwen3_xml', 'model': '/home/huangfukk/models/AWQ/Qwen3-4B-Instruct-2507', 'trust_remote_code': True, 'max_model_len': 16384, 'served_model_name': ['local-model'], 'max_parallel_loading_workers': 4, 'gpu_memory_utilization': 0.5, 'max_num_seqs': 1} +(APIServer pid=43538) The argument `trust_remote_code` is to be used with Auto classes. It has no effect here and is ignored. +(APIServer pid=43538) INFO 01-20 16:25:05 [model.py:514] Resolved architecture: Qwen3ForCausalLM +(APIServer pid=43538) INFO 01-20 16:25:05 [model.py:1661] Using max model len 16384 +(APIServer pid=43538) INFO 01-20 16:25:05 [awq_marlin.py:162] The model is convertible to awq_marlin during runtime. Using awq_marlin kernel. +(APIServer pid=43538) WARNING 01-20 16:25:05 [parallel.py:595] max_parallel_loading_workers is currently not supported and will be ignored. +(APIServer pid=43538) INFO 01-20 16:25:05 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=2048. +(APIServer pid=43538) WARNING 01-20 16:25:06 [interface.py:465] Using 'pin_memory=False' as WSL is detected. This may slow down the performance. +(EngineCore_DP0 pid=43785) INFO 01-20 16:25:10 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='/home/huangfukk/models/AWQ/Qwen3-4B-Instruct-2507', speculative_config=None, tokenizer='/home/huangfukk/models/AWQ/Qwen3-4B-Instruct-2507', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=True, dtype=torch.bfloat16, max_seq_len=16384, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=awq_marlin, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=local-model, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [2048], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 2, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None} +(EngineCore_DP0 pid=43785) INFO 01-20 16:25:11 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://192.168.31.62:43504 backend=nccl +[W120 16:25:21.543804205 socket.cpp:209] [c10d] The hostname of the client socket cannot be retrieved. err=-3 +(EngineCore_DP0 pid=43785) INFO 01-20 16:25:21 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0 +(EngineCore_DP0 pid=43785) WARNING 01-20 16:25:21 [interface.py:465] Using 'pin_memory=False' as WSL is detected. This may slow down the performance. +(EngineCore_DP0 pid=43785) INFO 01-20 16:25:22 [gpu_model_runner.py:3562] Starting to load model /home/huangfukk/models/AWQ/Qwen3-4B-Instruct-2507... +(EngineCore_DP0 pid=43785) INFO 01-20 16:25:22 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION') +(EngineCore_DP0 pid=43785) Loading safetensors checkpoint shards: 0% Completed | 0/1 [00:00system +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- if messages[0].role == 'system' -%} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {{ messages[0].content }} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- else -%} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] You are a helpful assistant. +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- endif -%} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] # Tools +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] You may call one or more functions to assist with the user query. +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] The function signatures are provided below: +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- for tool in tools %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {{ tool | tojson }} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- endfor %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] When you need to call a tool, respond with XML tags in this exact format: +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] value1 +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] value2 +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] <|im_end|> +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- else %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- if messages[0].role == 'system' -%} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] <|im_start|>system +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {{ messages[0].content }}<|im_end|> +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- endif -%} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- endif %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- for message in messages %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- if (message.role == "user") or (message.role == "system" and not loop.first) %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] <|im_start|>{{ message.role }} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {{ message.content }}<|im_end|> +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- elif message.role == "assistant" %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] <|im_start|>assistant +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- if message.content %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {{ message.content }} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- endif %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] <|im_end|> +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- elif message.role == "tool" %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] <|im_start|>user +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {{ message.content }} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] <|im_end|> +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- endif %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- endfor %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- if add_generation_prompt %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] <|im_start|>assistant +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] {%- endif %} +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] +(APIServer pid=43538) WARNING 01-20 16:25:42 [utils.py:312] It is different from official chat template '/home/huangfukk/models/AWQ/Qwen3-4B-Instruct-2507'. This discrepancy may lead to performance degradation. +(APIServer pid=43538) WARNING 01-20 16:25:42 [model.py:1487] Default sampling parameters have been overridden by the model's Hugging Face generation config recommended from the model creator. If this is not intended, please relaunch vLLM instance with `--generation-config vllm`. +(APIServer pid=43538) INFO 01-20 16:25:42 [serving_responses.py:201] Using default chat sampling params from model: {'temperature': 0.7, 'top_k': 20, 'top_p': 0.8} +(APIServer pid=43538) INFO 01-20 16:25:42 [serving_engine.py:270] "auto" tool choice has been enabled. +(APIServer pid=43538) INFO 01-20 16:25:42 [serving_engine.py:270] "auto" tool choice has been enabled. +(APIServer pid=43538) INFO 01-20 16:25:42 [serving_chat.py:137] Using default chat sampling params from model: {'temperature': 0.7, 'top_k': 20, 'top_p': 0.8} +(APIServer pid=43538) INFO 01-20 16:25:42 [serving_completion.py:77] Using default completion sampling params from model: {'temperature': 0.7, 'top_k': 20, 'top_p': 0.8} +(APIServer pid=43538) INFO 01-20 16:25:42 [serving_engine.py:270] "auto" tool choice has been enabled. +(APIServer pid=43538) INFO 01-20 16:25:42 [serving_chat.py:137] Using default chat sampling params from model: {'temperature': 0.7, 'top_k': 20, 'top_p': 0.8} +(APIServer pid=43538) INFO 01-20 16:25:42 [api_server.py:1425] Starting vLLM API server 0 on http://0.0.0.0:8081 +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:38] Available routes are: +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /openapi.json, Methods: GET, HEAD +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /docs, Methods: GET, HEAD +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /docs/oauth2-redirect, Methods: GET, HEAD +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /redoc, Methods: GET, HEAD +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /scale_elastic_ep, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /is_scaling_elastic_ep, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /tokenize, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /detokenize, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /inference/v1/generate, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /pause, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /resume, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /is_paused, Methods: GET +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /metrics, Methods: GET +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /health, Methods: GET +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /load, Methods: GET +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/models, Methods: GET +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /version, Methods: GET +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/responses, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/responses/{response_id}, Methods: GET +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/responses/{response_id}/cancel, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/messages, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/chat/completions, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/completions, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/audio/transcriptions, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/audio/translations, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /ping, Methods: GET +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /ping, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /invocations, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /classify, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/embeddings, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /score, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/score, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /rerank, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v1/rerank, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /v2/rerank, Methods: POST +(APIServer pid=43538) INFO 01-20 16:25:42 [launcher.py:46] Route: /pooling, Methods: POST +(APIServer pid=43538) INFO: Started server process [43538] +(APIServer pid=43538) INFO: Waiting for application startup. +(APIServer pid=43538) INFO: Application startup complete. +(APIServer pid=43538) INFO: 127.0.0.1:46791 - "GET /health HTTP/1.1" 200 OK +(APIServer pid=43538) INFO: 127.0.0.1:43619 - "POST /v1/chat/completions HTTP/1.1" 200 OK +(APIServer pid=43538) INFO 01-21 20:22:50 [loggers.py:248] Engine 000: Avg prompt throughput: 168.4 tokens/s, Avg generation throughput: 0.6 tokens/s, Running: 0 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.0%, Prefix cache hit rate: 0.0% +(APIServer pid=43538) INFO 01-21 20:23:00 [loggers.py:248] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.0%, Prefix cache hit rate: 0.0% diff --git a/start_all.sh b/start_all.sh index ce5d19c9..798be2ea 100755 --- a/start_all.sh +++ b/start_all.sh @@ -224,16 +224,6 @@ start_services() { print_success "找到Embedding模型: ${embedding_model_expanded}" echo "" - # 检查ROS2环境 - local ros2_setup="${PROJECT_ROOT}/install/setup.bash" - if [ ! -f "${ros2_setup}" ]; then - print_warning "ROS2 setup文件未找到: ${ros2_setup}" - print_warning "如果项目已与ROS2解耦,可以忽略此警告" - else - print_success "找到ROS2 setup文件: ${ros2_setup}" - fi - echo "" - # 检查venv虚拟环境 local venv_path_expanded=$(eval echo "${VENV_PATH}") print_info "检查venv虚拟环境: ${venv_path_expanded}" @@ -357,11 +347,6 @@ start_services() { # 激活venv虚拟环境并启动FastAPI服务 # 使用bash -c来在新的shell中激活venv环境 bash -c " - # 激活ROS2环境(如果存在) - if [ -f '${ros2_setup}' ]; then - source '${ros2_setup}' - fi - # 确保 Graphviz 在 PATH 中 if [ -n '${DOT_DIR}' ]; then export PATH=\"${DOT_DIR}:\$PATH\" diff --git a/start_all_vllm.sh b/start_all_vllm.sh index 4b8fabdc..2d2626f0 100755 --- a/start_all_vllm.sh +++ b/start_all_vllm.sh @@ -209,16 +209,6 @@ start_services() { print_success "找到Embedding模型: ${embedding_model_expanded}" echo "" - # 检查ROS2环境 - local ros2_setup="${PROJECT_ROOT}/install/setup.bash" - if [ ! -f "${ros2_setup}" ]; then - print_warning "ROS2 setup文件未找到: ${ros2_setup}" - print_warning "如果项目已与ROS2解耦,可以忽略此警告" - else - print_success "找到ROS2 setup文件: ${ros2_setup}" - fi - echo "" - # 检查venv虚拟环境 local venv_path_expanded venv_path_expanded=$(eval echo "${VENV_PATH}") @@ -355,11 +345,6 @@ start_services() { fi bash -c " - # 激活ROS2环境(如果存在) - if [ -f '${ros2_setup}' ]; then - source '${ros2_setup}' - fi - # 确保 Graphviz 在 PATH 中 if [ -n '${DOT_DIR}' ]; then export PATH=\"${DOT_DIR}:\$PATH\" diff --git a/tools/__pycache__/ground_station_client.cpython-313.pyc b/tools/__pycache__/ground_station_client.cpython-313.pyc new file mode 100644 index 00000000..c80bb969 Binary files /dev/null and b/tools/__pycache__/ground_station_client.cpython-313.pyc differ diff --git a/tools/__pycache__/test_api.cpython-313.pyc b/tools/__pycache__/test_api.cpython-313.pyc new file mode 100644 index 00000000..faae3cc1 Binary files /dev/null and b/tools/__pycache__/test_api.cpython-313.pyc differ diff --git a/tools/__pycache__/test_llama_server.cpython-313.pyc b/tools/__pycache__/test_llama_server.cpython-313.pyc new file mode 100644 index 00000000..61312e14 Binary files /dev/null and b/tools/__pycache__/test_llama_server.cpython-313.pyc differ diff --git a/tools/api_test.log b/tools/api_test.log index 5bebe89d..3d4b192b 100644 --- a/tools/api_test.log +++ b/tools/api_test.log @@ -2165,3 +2165,7 @@ [2025-12-06 13:42:34] ✅ Test completed successfully [2025-12-06 13:42:34] ================================================================================ [2025-12-06 13:42:34] +[2026-02-03 22:26:39] ================================================================================ +[2026-02-03 22:26:39] --- API Test: Generate Plan --- +[2026-02-03 22:26:39] URL: http://127.0.0.1:8000/generate_plan +[2026-02-03 22:26:39] Sending Prompt: "无人机起飞到80米高度后,先移动至搜索区,搜索并锁定任一红色车辆,跟踪接近距离目标车辆10m后进行拍照,完成拍照后返回。" diff --git a/tools/build_knowledge_base.py b/tools/build_knowledge_base.py deleted file mode 100644 index 19ffaba3..00000000 --- a/tools/build_knowledge_base.py +++ /dev/null @@ -1,157 +0,0 @@ -import xml.etree.ElementTree as ET -import json -from pathlib import Path -import logging -import os - -# --- 配置日志 --- -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') - -def process_osm_json(input_path: Path) -> list[str]: - """ - 处理OpenStreetMap的JSON文件,返回描述性句子列表。 - """ - logging.info(f"正在以OSM JSON格式处理文件: {input_path.name}") - descriptions = [] - try: - with open(input_path, 'r', encoding='utf-8') as f: - data = json.load(f) - except (json.JSONDecodeError, IOError) as e: - logging.error(f"读取或解析 {input_path.name} 时出错: {e}") - return [] - - elements = data.get('elements', []) - if not elements: - return [] - - nodes_map = {node['id']: node for node in elements if node.get('type') == 'node'} - ways = [elem for elem in elements if elem.get('type') == 'way'] - - for way in ways: - tags = way.get('tags', {}) - if 'name' not in tags: - continue - - way_name = tags.get('name') - way_nodes_ids = way.get('nodes', []) - if not way_nodes_ids: - continue - - total_lat, total_lon, node_count = 0, 0, 0 - for node_id in way_nodes_ids: - node_info = nodes_map.get(node_id) - if node_info: - total_lat += node_info.get('lat', 0) - total_lon += node_info.get('lon', 0) - node_count += 1 - - if node_count == 0: - continue - - center_lat = total_lat / node_count - center_lon = total_lon / node_count - - sentence = f"在地图上有一个名为 '{way_name}' 的地点或区域" - other_tags = {k: v for k, v in tags.items() if k != 'name'} - if other_tags: - tag_descs = [f"{key}是'{value}'" for key, value in other_tags.items()] - sentence += f",它的{ '、'.join(tag_descs) }" - sentence += f",其中心位置坐标大约在 ({center_lat:.6f}, {center_lon:.6f})。" - descriptions.append(sentence) - - logging.info(f"从 {input_path.name} 提取了 {len(descriptions)} 条位置描述。") - return descriptions - - -def process_gazebo_world(input_path: Path) -> list[str]: - """ - 处理Gazebo的.world文件,返回描述性句子列表。 - """ - logging.info(f"正在以Gazebo World格式处理文件: {input_path.name}") - descriptions = [] - try: - tree = ET.parse(input_path) - root = tree.getroot() - except ET.ParseError as e: - logging.error(f"解析XML文件 {input_path.name} 失败: {e}") - return [] - - models = root.findall('.//model') - for model in models: - model_name = model.get('name') - pose_element = model.find('pose') - - if model_name and pose_element is not None and pose_element.text: - try: - pose_values = [float(p) for p in pose_element.text.strip().split()] - sentence = ( - f"仿真环境中有一个名为 '{model_name}' 的物体," - f"其位置和姿态(x, y, z, roll, pitch, yaw)为: {pose_values}。" - ) - descriptions.append(sentence) - except (ValueError, IndexError): - logging.warning(f"跳过模型 '{model_name}',因其pose格式不正确。") - - logging.info(f"从 {input_path.name} 提取了 {len(descriptions)} 个物体信息。") - return descriptions - - -def main(): - """ - 主函数,扫描源数据目录,为每个文件生成独立的NDJSON知识库。 - """ - script_dir = Path(__file__).resolve().parent - # 输入源: tools/map/ - source_data_dir = script_dir / 'map' - # 输出目录: tools/knowledge_base/ - output_knowledge_base_dir = script_dir / 'knowledge_base' - - if not source_data_dir.exists(): - logging.error(f"源数据目录不存在: {source_data_dir}") - return - - # 确保输出目录存在 - output_knowledge_base_dir.mkdir(parents=True, exist_ok=True) - - total_files_processed = 0 - logging.info(f"--- 开始扫描源数据目录: {source_data_dir} ---") - - for file_path in source_data_dir.iterdir(): - if not file_path.is_file(): - continue - - descriptions = [] - if file_path.suffix == '.json': - descriptions = process_osm_json(file_path) - elif file_path.suffix == '.world': - descriptions = process_gazebo_world(file_path) - else: - logging.warning(f"跳过不支持的文件类型: {file_path.name}") - continue - - if not descriptions: - logging.warning(f"未能从 {file_path.name} 提取有效信息,跳过生成文件。") - continue - - output_filename = file_path.stem + '_knowledge.ndjson' - output_path = output_knowledge_base_dir / output_filename - - try: - with open(output_path, 'w', encoding='utf-8') as f: - for sentence in descriptions: - json_record = {"text": sentence} - f.write(json.dumps(json_record, ensure_ascii=False) + '\n') - logging.info(f"成功为 '{file_path.name}' 生成知识库文件: {output_path.name}") - total_files_processed += 1 - except IOError as e: - logging.error(f"写入输出文件 '{output_path.name}' 失败: {e}") - - logging.info("--- 数据处理完成 ---") - if total_files_processed > 0: - logging.info(f"共为 {total_files_processed} 个源文件生成了知识库。") - else: - logging.warning("未生成任何知识库文件。") - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/tools/ingest.py b/tools/ingest.py deleted file mode 100644 index 476a3ece..00000000 --- a/tools/ingest.py +++ /dev/null @@ -1,194 +0,0 @@ -# 该代码用于将本地知识库中的文档导入到ChromaDB中,并使用远程嵌入模型进行向量化 -import os -from pathlib import Path -import chromadb -# from chromadb.utils import embedding_functions - 不再需要 -from chromadb.api.types import Documents, EmbeddingFunction, Embeddings, Embeddable -from unstructured.partition.auto import partition -from rich.progress import track -import logging -import requests # 导入requests -import json # 导入json模块 - -# 配置日志 -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') - -# --- 配置 --- -# 获取脚本所在目录,确保路径的正确性 -SCRIPT_DIR = Path(__file__).resolve().parent -KNOWLEDGE_BASE_DIR = SCRIPT_DIR / "knowledge_base" -VECTOR_STORE_DIR = SCRIPT_DIR / "vector_store" -COLLECTION_NAME = "drone_docs" -# EMBEDDING_MODEL_NAME = "bge-small-zh-v1.5" # 不再需要,模型名在函数内部处理 - -# --- 自定义远程嵌入函数 --- -class RemoteEmbeddingFunction(EmbeddingFunction[Embeddable]): - """ - 一个使用远程、兼容OpenAI API的嵌入服务的嵌入函数。 - """ - def __init__(self, api_url: str): - self._api_url = api_url - logging.info(f"自定义嵌入函数已初始化,将连接到: {self._api_url}") - - def __call__(self, input: Embeddable) -> Embeddings: - """ - 对输入的文档进行嵌入。 - """ - # 我们的服务只能处理文本,所以检查输入是否为字符串列表 - if not isinstance(input, list) or not all(isinstance(doc, str) for doc in input): - logging.error("此嵌入函数仅支持字符串列表(文档)作为输入。") - return [] - - try: - # 移除 "model" 参数,因为embedding服务可能不需要它 - response = requests.post( - self._api_url, - json={"input": input}, - headers={"Content-Type": "application/json"} - ) - response.raise_for_status() # 如果请求失败则抛出HTTPError - - # 按照OpenAI API的格式解析返回的嵌入向量 - data = response.json().get("data", []) - if not data: - raise ValueError("API响应中没有找到'data'字段或'data'为空") - - embeddings = [item['embedding'] for item in data] - return embeddings - - except requests.RequestException as e: - logging.error(f"调用嵌入API失败: {e}") - # 返回一个空列表或根据需要处理错误 - return [] - except (ValueError, KeyError) as e: - logging.error(f"解析API响应失败: {e}") - logging.error(f"收到的响应内容: {response.text}") - return [] - - -def get_documents(directory: Path): - """从知识库目录加载所有文档并进行切分""" - documents = [] - logging.info(f"从 '{directory}' 加载文档...") - for file_path in directory.rglob("*"): - if file_path.is_file() and not file_path.name.startswith('.'): - try: - # 对简单文本文件直接读取 - if file_path.suffix in ['.txt', '.md']: - text = file_path.read_text(encoding='utf-8') - documents.append({ - "text": text, - "metadata": {"source": str(file_path.name)} - }) - logging.info(f"成功处理文本文件: {file_path.name}") - # 特别处理常规的JSON文件 - elif file_path.suffix == '.json': - with open(file_path, 'r', encoding='utf-8') as f: - data = json.load(f) - if 'elements' in data and isinstance(data['elements'], list): - for element in data['elements']: - documents.append({ - "text": json.dumps(element, ensure_ascii=False), - "metadata": {"source": str(file_path.name)} - }) - logging.info(f"成功处理JSON文件: {file_path.name}, 提取了 {len(data['elements'])} 个元素。") - else: - documents.append({ - "text": json.dumps(data, ensure_ascii=False), - "metadata": {"source": str(file_path.name)} - }) - logging.info(f"成功处理JSON文件: {file_path.name} (作为单个文档)") - # 新增:专门处理我们生成的 NDJSON 文件 - elif file_path.suffix == '.ndjson': - with open(file_path, 'r', encoding='utf-8') as f: - count = 0 - for line in f: - try: - record = json.loads(line) - if 'text' in record and isinstance(record['text'], str): - documents.append({ - "text": record['text'], - "metadata": {"source": str(file_path.name)} - }) - count += 1 - except json.JSONDecodeError: - logging.warning(f"跳过无效的JSON行: {line.strip()}") - if count > 0: - logging.info(f"成功处理NDJSON文件: {file_path.name}, 提取了 {count} 个文档。") - # 对其他所有文件类型,使用unstructured - else: - elements = partition(filename=str(file_path)) - for element in elements: - documents.append({ - "text": element.text, - "metadata": {"source": str(file_path.name)} - }) - logging.info(f"成功处理文件: {file_path.name} (使用unstructured)") - except Exception as e: - logging.error(f"处理文件 {file_path.name} 失败: {e}") - return documents - - -def main(): - """主函数,执行文档入库流程""" - if not KNOWLEDGE_BASE_DIR.exists(): - KNOWLEDGE_BASE_DIR.mkdir(parents=True) - logging.warning(f"知识库目录不存在,已自动创建: {KNOWLEDGE_BASE_DIR}") - logging.warning("请向该目录中添加您的知识文件(如 .txt, .pdf, .md)。") - return - - # 1. 加载并切分文档 - docs_to_ingest = get_documents(KNOWLEDGE_BASE_DIR) - if not docs_to_ingest: - logging.warning("在知识库中未找到可处理的文档。") - return - - # 2. 初始化ChromaDB客户端和远程嵌入函数 - orin_ip = os.getenv("ORIN_IP", "localhost") - embedding_api_url = f"http://{orin_ip}:8090/v1/embeddings" - - logging.info(f"正在初始化远程嵌入函数,目标服务地址: {embedding_api_url}") - embedding_func = RemoteEmbeddingFunction(api_url=embedding_api_url) - - client = chromadb.PersistentClient(path=str(VECTOR_STORE_DIR)) - - # 3. 创建或获取集合 - logging.info(f"正在访问ChromaDB集合: {COLLECTION_NAME}") - collection = client.get_or_create_collection( - name=COLLECTION_NAME, - embedding_function=embedding_func - ) - - # 4. 将文档向量化并存入数据库 - logging.info(f"开始将 {len(docs_to_ingest)} 个文档块入库...") - - # 为了避免重复添加,可以先检查 - # (这里为了简单,我们每次都重新添加,生产环境需要更复杂的逻辑) - - doc_texts = [doc['text'] for doc in docs_to_ingest] - metadatas = [doc['metadata'] for doc in docs_to_ingest] - ids = [f"doc_{KNOWLEDGE_BASE_DIR.name}_{i}" for i in range(len(doc_texts))] - - try: - # ChromaDB的add方法会自动处理嵌入 - collection.add( - documents=doc_texts, - metadatas=metadatas, - ids=ids - ) - logging.info("所有文档块已成功入库!") - except Exception as e: - logging.error(f"向ChromaDB添加文档时出错: {e}") - - - # 验证一下 - count = collection.count() - logging.info(f"数据库中现在有 {count} 个条目。") - - print("\n✅ 数据入库完成!") - print(f"知识库位于: {KNOWLEDGE_BASE_DIR}") - print(f"向量数据库位于: {VECTOR_STORE_DIR}") - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/tools/rag/README.md b/tools/rag/README.md index 4fe96cfe..bfa119fe 100644 --- a/tools/rag/README.md +++ b/tools/rag/README.md @@ -1,39 +1,62 @@ -# RAG & Map Tools +# RAG 工具说明 -该目录包含了地图构建、知识库生成和向量数据库管理的相关工具。 +该目录负责知识构建与向量入库。 ## 目录结构 -- **knowledge_base/**: 存放源文档数据。 - - 支持格式: `.txt`, `.md`, `.pdf` - - 生成格式: `.json`, `.ndjson` (由 `build_knowledge_base.py` 生成) - -- **map/**: 存放地图原始数据。 - - `.osm` (OpenStreetMap 数据) - - `.world` (Gazebo 仿真环境数据) - -- **vector_store/**: ChromaDB 向量数据库的持久化存储目录。 - -## 脚本说明 - -### 1. `build_knowledge_base.py` -**功能**: 处理 `map/` 目录下的地图文件,提取地理信息和语义描述,生成知识库文件到 `knowledge_base/` 目录。 -**使用方法**: -```bash -python build_knowledge_base.py +```text +tools/rag/ +├── map/ # 原始地图数据 +├── knowledge_base/ +│ ├── location/ # 地点知识(可选分目录) +│ ├── pattern/ # 模式知识(任务模板) +│ ├── rules/ # 规则知识 +│ └── *.ndjson +├── vector_store/ # Chroma 持久化目录 +├── build_knowledge_base.py # 从 map 构建 ndjson +└── ingest.py # 入库到 Chroma(多集合 + 兼容集合) ``` -### 2. `ingest.py` -**功能**: 读取 `knowledge_base/` 中的所有文档,调用嵌入模型(Embedding Model)将其向量化,并存入 `vector_store/` 中的 ChromaDB 数据库。 -**使用方法**: +## 运行前准备 + ```bash -python ingest.py +cd /home/huangfukk/DronePlanning +source backend_service/venv/bin/activate +export ORIN_IP="localhost" ``` -**依赖**: 需要确保后端嵌入服务(如 `llama-server`)已启动,或者配置正确的 `ORIN_IP` 环境变量。 -## 工作流 -1. 将地图文件放入 `map/`。 -2. 运行 `build_knowledge_base.py` 生成文本描述。 -3. 将其他补充文档放入 `knowledge_base/`。 -4. 运行 `ingest.py` 构建向量索引。 +并确保 embedding 服务可用(默认 `8090`): + +```bash +curl -s http://localhost:8090/v1/embeddings \ + -H "Content-Type: application/json" \ + -d '{"input":["test"]}' +``` + +## 完整流程(可直接执行) + +```bash +cd /home/huangfukk/DronePlanning +source backend_service/venv/bin/activate + +# 1) 由 map 构建知识文本 +python tools/rag/build_knowledge_base.py + +# 2) 入库 +python tools/rag/ingest.py +``` + +## 入库结果 + +`ingest.py` 会同时写入: + +- 兼容集合:`drone_docs` +- 新集合:`location_kb`、`pattern_kb`、`rules_kb` + +并在 metadata 中写入 `kb_type`,支持检索时按知识域筛选。 + +## 常见问题 + +- **Chroma 初始化异常**:先确认使用的是项目 venv,再检查 `tools/rag/vector_store/` 是否损坏(备份后重建)。 +- **连接 embedding 失败**:检查 `ORIN_IP`、端口 8090、以及模型服务是否已启动。 diff --git a/tools/rag/__pycache__/build_knowledge_base.cpython-313.pyc b/tools/rag/__pycache__/build_knowledge_base.cpython-313.pyc new file mode 100644 index 00000000..14b80b87 Binary files /dev/null and b/tools/rag/__pycache__/build_knowledge_base.cpython-313.pyc differ diff --git a/tools/rag/__pycache__/ingest.cpython-313.pyc b/tools/rag/__pycache__/ingest.cpython-313.pyc new file mode 100644 index 00000000..31d749ca Binary files /dev/null and b/tools/rag/__pycache__/ingest.cpython-313.pyc differ diff --git a/tools/rag/ingest.py b/tools/rag/ingest.py index 240040e8..ae9821ec 100644 --- a/tools/rag/ingest.py +++ b/tools/rag/ingest.py @@ -5,7 +5,6 @@ import chromadb # from chromadb.utils import embedding_functions - 不再需要 from chromadb.api.types import Documents, EmbeddingFunction, Embeddings, Embeddable from unstructured.partition.auto import partition -from rich.progress import track import logging import requests # 导入requests import json # 导入json模块 @@ -19,6 +18,11 @@ SCRIPT_DIR = Path(__file__).resolve().parent KNOWLEDGE_BASE_DIR = SCRIPT_DIR / "knowledge_base" VECTOR_STORE_DIR = SCRIPT_DIR / "vector_store" COLLECTION_NAME = "drone_docs" +COLLECTIONS_BY_KB_TYPE = { + "location": "location_kb", + "pattern": "pattern_kb", + "rules": "rules_kb", +} # EMBEDDING_MODEL_NAME = "bge-small-zh-v1.5" # 不再需要,模型名在函数内部处理 # --- 自定义远程嵌入函数 --- @@ -66,6 +70,18 @@ class RemoteEmbeddingFunction(EmbeddingFunction[Embeddable]): return [] +def _infer_kb_type(file_path: Path, base_dir: Path) -> str: + try: + rel = file_path.relative_to(base_dir) + except ValueError: + return "location" + if len(rel.parts) > 1: + first = rel.parts[0].lower() + if first in COLLECTIONS_BY_KB_TYPE: + return first + return "location" + + def get_documents(directory: Path): """从知识库目录加载所有文档并进行切分""" documents = [] @@ -74,11 +90,12 @@ def get_documents(directory: Path): if file_path.is_file() and not file_path.name.startswith('.'): try: # 对简单文本文件直接读取 + kb_type = _infer_kb_type(file_path, directory) if file_path.suffix in ['.txt', '.md']: text = file_path.read_text(encoding='utf-8') documents.append({ "text": text, - "metadata": {"source": str(file_path.name)} + "metadata": {"source": str(file_path.name), "kb_type": kb_type} }) logging.info(f"成功处理文本文件: {file_path.name}") # 特别处理常规的JSON文件 @@ -89,13 +106,13 @@ def get_documents(directory: Path): for element in data['elements']: documents.append({ "text": json.dumps(element, ensure_ascii=False), - "metadata": {"source": str(file_path.name)} + "metadata": {"source": str(file_path.name), "kb_type": kb_type} }) logging.info(f"成功处理JSON文件: {file_path.name}, 提取了 {len(data['elements'])} 个元素。") else: documents.append({ "text": json.dumps(data, ensure_ascii=False), - "metadata": {"source": str(file_path.name)} + "metadata": {"source": str(file_path.name), "kb_type": kb_type} }) logging.info(f"成功处理JSON文件: {file_path.name} (作为单个文档)") # 新增:专门处理我们生成的 NDJSON 文件 @@ -115,7 +132,7 @@ def get_documents(directory: Path): text = json.dumps(record, ensure_ascii=False) documents.append({ "text": text, - "metadata": {"source": str(file_path.name)} + "metadata": {"source": str(file_path.name), "kb_type": kb_type} }) count += 1 except json.JSONDecodeError: @@ -128,7 +145,7 @@ def get_documents(directory: Path): for element in elements: documents.append({ "text": element.text, - "metadata": {"source": str(file_path.name)} + "metadata": {"source": str(file_path.name), "kb_type": kb_type} }) logging.info(f"成功处理文件: {file_path.name} (使用unstructured)") except Exception as e: @@ -159,38 +176,48 @@ def main(): client = chromadb.PersistentClient(path=str(VECTOR_STORE_DIR)) - # 3. 创建或获取集合 - logging.info(f"正在访问ChromaDB集合: {COLLECTION_NAME}") - collection = client.get_or_create_collection( - name=COLLECTION_NAME, - embedding_function=embedding_func - ) + # 3. 创建或获取集合(兼容旧集合 + 新多知识库集合) + collections = { + "fallback": client.get_or_create_collection(name=COLLECTION_NAME, embedding_function=embedding_func) + } + for kb_type, coll_name in COLLECTIONS_BY_KB_TYPE.items(): + collections[kb_type] = client.get_or_create_collection(name=coll_name, embedding_function=embedding_func) # 4. 将文档向量化并存入数据库 logging.info(f"开始将 {len(docs_to_ingest)} 个文档块入库...") - - # 为了避免重复添加,可以先检查 - # (这里为了简单,我们每次都重新添加,生产环境需要更复杂的逻辑) - - doc_texts = [doc['text'] for doc in docs_to_ingest] - metadatas = [doc['metadata'] for doc in docs_to_ingest] - ids = [f"doc_{KNOWLEDGE_BASE_DIR.name}_{i}" for i in range(len(doc_texts))] + grouped_docs = {} + for doc in docs_to_ingest: + kb_type = doc["metadata"].get("kb_type", "location") + grouped_docs.setdefault(kb_type, []).append(doc) + # 先入旧集合,保持兼容 + all_texts = [doc['text'] for doc in docs_to_ingest] + all_metadatas = [doc['metadata'] for doc in docs_to_ingest] + all_ids = [f"doc_{KNOWLEDGE_BASE_DIR.name}_{i}" for i in range(len(all_texts))] try: - # ChromaDB的add方法会自动处理嵌入 - collection.add( - documents=doc_texts, - metadatas=metadatas, - ids=ids - ) - logging.info("所有文档块已成功入库!") + collections["fallback"].add(documents=all_texts, metadatas=all_metadatas, ids=all_ids) + logging.info("兼容集合 drone_docs 入库完成。") except Exception as e: - logging.error(f"向ChromaDB添加文档时出错: {e}") + logging.error(f"向兼容集合添加文档时出错: {e}") + for kb_type, docs in grouped_docs.items(): + if kb_type not in collections: + continue + doc_texts = [doc["text"] for doc in docs] + metadatas = [doc["metadata"] for doc in docs] + ids = [f"doc_{kb_type}_{i}" for i in range(len(doc_texts))] + try: + collections[kb_type].add(documents=doc_texts, metadatas=metadatas, ids=ids) + logging.info(f"{kb_type} 集合入库完成,条目数: {len(doc_texts)}") + except Exception as e: + logging.error(f"向 {kb_type} 集合添加文档时出错: {e}") - # 验证一下 - count = collection.count() - logging.info(f"数据库中现在有 {count} 个条目。") + for name, collection in collections.items(): + try: + count = collection.count() + logging.info(f"集合 {name} 当前条目数: {count}") + except Exception: + pass print("\n✅ 数据入库完成!") print(f"知识库位于: {KNOWLEDGE_BASE_DIR}") diff --git a/tools/rag/knowledge_base/pattern/scene_patterns.ndjson b/tools/rag/knowledge_base/pattern/scene_patterns.ndjson new file mode 100644 index 00000000..8be65236 --- /dev/null +++ b/tools/rag/knowledge_base/pattern/scene_patterns.ndjson @@ -0,0 +1,6 @@ +{"scene_id":"scene1_perimeter_window_ground","intent_type":"patrol_or_monitor","trigger_terms":["面前大楼","12米","外围","窗户"],"text":"地面起飞后到面前大楼约12米高度,沿外围巡查打开窗户;如发现窗户则拍照回传。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},{"type":"action","name":"takeoff","params":{"altitude":12}},{"type":"action","name":"rotate_search","params":{"target_class":"window"}},{"type":"condition","name":"object_detected","params":{"target_class":"window"}},{"type":"action","name":"take_photos","params":{"target_class":"window","track_time":10}}]}}} +{"scene_id":"scene1_perimeter_person_air","intent_type":"patrol_or_monitor","trigger_terms":["这栋楼","空中","外围","人"],"text":"无人机已在空中,先调整高度后沿建筑外围巡查人员,发现目标后拍照。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"move_direction","params":{"direction":"up","distance":3}},{"type":"action","name":"rotate_search","params":{"target_class":"person"}},{"type":"condition","name":"object_detected","params":{"target_class":"person"}},{"type":"action","name":"take_photos","params":{"target_class":"person","track_time":10}}]}}} +{"scene_id":"scene1_perimeter_trash_ground","intent_type":"patrol_or_monitor","trigger_terms":["面前大楼","杂物","外围"],"text":"地面起飞至指定高度后沿楼体外围侦察杂物堆积并拍照。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"takeoff","params":{"altitude":12}},{"type":"action","name":"rotate_search","params":{"target_class":"garbage"}},{"type":"condition","name":"object_detected","params":{"target_class":"garbage"}},{"type":"action","name":"take_photos","params":{"target_class":"garbage","track_time":8}}]}}} +{"scene_id":"scene4_named_place_search_photo","intent_type":"search_and_photo","trigger_terms":["广场","查找","拍照"],"text":"前往命名地点后搜索目标并拍照。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"fly_to_waypoint","params":{"x":0,"y":0,"z":10,"acceptance_radius":2}},{"type":"action","name":"rotate_search","params":{"target_class":"person"}},{"type":"condition","name":"object_detected","params":{"target_class":"person"}},{"type":"action","name":"take_photos","params":{"target_class":"person","track_time":8}}]}}} +{"scene_id":"scene4_named_place_monitor_return","intent_type":"return_or_land","trigger_terms":["广场南边","监控","返航"],"text":"先到达命名地点偏移区域执行监控,到时后返航。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"fly_to_waypoint","params":{"x":40,"y":80,"z":15,"acceptance_radius":2}},{"type":"action","name":"loiter","params":{"duration":300}},{"type":"action","name":"return_emergency","params":{"reason":"mission_complete"}}]}}} +{"scene_id":"scene4_named_place_confirm_then_action","intent_type":"search_and_photo","trigger_terms":["确认后","拍照","返航"],"text":"在命名区域发现目标后等待人工确认,再执行拍照或返航。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"fly_to_waypoint","params":{"x":10,"y":10,"z":10,"acceptance_radius":2}},{"type":"action","name":"rotate_search","params":{"target_class":"car"}},{"type":"condition","name":"object_detected","params":{"target_class":"car"}},{"type":"action","name":"manual_confirmation","params":{}},{"type":"action","name":"take_photos","params":{"target_class":"car","track_time":8}}]}}} diff --git a/tools/rag/knowledge_base/rules/flight_rules.ndjson b/tools/rag/knowledge_base/rules/flight_rules.ndjson new file mode 100644 index 00000000..1b893fe2 --- /dev/null +++ b/tools/rag/knowledge_base/rules/flight_rules.ndjson @@ -0,0 +1,3 @@ +{"rule_id":"rules_manual_confirmation","intent_type":"search_and_photo","trigger_terms":["我确认","等待确认","经允许"],"text":"仅当用户明确要求人工确认时才注入 manual_confirmation 节点;否则禁止主动添加。"} +{"rule_id":"rules_direction_priority","intent_type":"generic_mission","trigger_terms":["东边","西边","北边","南边","米"],"text":"当只有方向+距离且无具体地点名词时,优先使用 move_direction;不要生成 fly_to_waypoint。"} +{"rule_id":"rules_return_emergency_scope","intent_type":"return_or_land","trigger_terms":["返航","回到","去"],"text":"有明确目的地时使用 fly_to_waypoint;无明确目的地的立即返航才使用 return_emergency。"} diff --git a/tools/rag/vector_store/23bb3f6e-b7de-4238-9726-2e27241977fd/data_level0.bin b/tools/rag/vector_store/35d33512-f3ca-4d46-b119-37fac77983c4/data_level0.bin similarity index 100% rename from tools/rag/vector_store/23bb3f6e-b7de-4238-9726-2e27241977fd/data_level0.bin rename to tools/rag/vector_store/35d33512-f3ca-4d46-b119-37fac77983c4/data_level0.bin diff --git a/tools/rag/vector_store/23bb3f6e-b7de-4238-9726-2e27241977fd/header.bin b/tools/rag/vector_store/35d33512-f3ca-4d46-b119-37fac77983c4/header.bin similarity index 100% rename from tools/rag/vector_store/23bb3f6e-b7de-4238-9726-2e27241977fd/header.bin rename to tools/rag/vector_store/35d33512-f3ca-4d46-b119-37fac77983c4/header.bin diff --git a/tools/rag/vector_store/35d33512-f3ca-4d46-b119-37fac77983c4/length.bin b/tools/rag/vector_store/35d33512-f3ca-4d46-b119-37fac77983c4/length.bin new file mode 100644 index 00000000..2e254cfc Binary files /dev/null and b/tools/rag/vector_store/35d33512-f3ca-4d46-b119-37fac77983c4/length.bin differ diff --git a/tools/rag/vector_store/23bb3f6e-b7de-4238-9726-2e27241977fd/link_lists.bin b/tools/rag/vector_store/35d33512-f3ca-4d46-b119-37fac77983c4/link_lists.bin similarity index 100% rename from tools/rag/vector_store/23bb3f6e-b7de-4238-9726-2e27241977fd/link_lists.bin rename to tools/rag/vector_store/35d33512-f3ca-4d46-b119-37fac77983c4/link_lists.bin diff --git a/tools/rag/vector_store/3c6eed41-9248-45c6-bfe2-cc2a94f08c04/length.bin b/tools/rag/vector_store/3c6eed41-9248-45c6-bfe2-cc2a94f08c04/length.bin deleted file mode 100644 index 62ac4452..00000000 Binary files a/tools/rag/vector_store/3c6eed41-9248-45c6-bfe2-cc2a94f08c04/length.bin and /dev/null differ diff --git a/tools/rag/vector_store/3c6eed41-9248-45c6-bfe2-cc2a94f08c04/data_level0.bin b/tools/rag/vector_store/8a7c9654-52bb-43d2-ad75-88521f71e816/data_level0.bin similarity index 100% rename from tools/rag/vector_store/3c6eed41-9248-45c6-bfe2-cc2a94f08c04/data_level0.bin rename to tools/rag/vector_store/8a7c9654-52bb-43d2-ad75-88521f71e816/data_level0.bin diff --git a/tools/rag/vector_store/3c6eed41-9248-45c6-bfe2-cc2a94f08c04/header.bin b/tools/rag/vector_store/8a7c9654-52bb-43d2-ad75-88521f71e816/header.bin similarity index 100% rename from tools/rag/vector_store/3c6eed41-9248-45c6-bfe2-cc2a94f08c04/header.bin rename to tools/rag/vector_store/8a7c9654-52bb-43d2-ad75-88521f71e816/header.bin diff --git a/tools/rag/vector_store/8a7c9654-52bb-43d2-ad75-88521f71e816/length.bin b/tools/rag/vector_store/8a7c9654-52bb-43d2-ad75-88521f71e816/length.bin new file mode 100644 index 00000000..17985d9c Binary files /dev/null and b/tools/rag/vector_store/8a7c9654-52bb-43d2-ad75-88521f71e816/length.bin differ diff --git a/tools/rag/vector_store/3c6eed41-9248-45c6-bfe2-cc2a94f08c04/link_lists.bin b/tools/rag/vector_store/8a7c9654-52bb-43d2-ad75-88521f71e816/link_lists.bin similarity index 100% rename from tools/rag/vector_store/3c6eed41-9248-45c6-bfe2-cc2a94f08c04/link_lists.bin rename to tools/rag/vector_store/8a7c9654-52bb-43d2-ad75-88521f71e816/link_lists.bin diff --git a/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/data_level0.bin b/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/data_level0.bin new file mode 100644 index 00000000..c656b189 Binary files /dev/null and b/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/data_level0.bin differ diff --git a/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/header.bin b/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/header.bin new file mode 100644 index 00000000..e89c5c59 Binary files /dev/null and b/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/header.bin differ diff --git a/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/length.bin b/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/length.bin new file mode 100644 index 00000000..171fc268 Binary files /dev/null and b/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/length.bin differ diff --git a/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/link_lists.bin b/tools/rag/vector_store/b41c0d31-a4ab-4820-9bbc-4b7293df3f2c/link_lists.bin new file mode 100644 index 00000000..e69de29b diff --git a/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/data_level0.bin b/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/data_level0.bin new file mode 100644 index 00000000..c656b189 Binary files /dev/null and b/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/data_level0.bin differ diff --git a/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/header.bin b/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/header.bin new file mode 100644 index 00000000..e89c5c59 Binary files /dev/null and b/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/header.bin differ diff --git a/tools/rag/vector_store/23bb3f6e-b7de-4238-9726-2e27241977fd/length.bin b/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/length.bin similarity index 84% rename from tools/rag/vector_store/23bb3f6e-b7de-4238-9726-2e27241977fd/length.bin rename to tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/length.bin index cb3e1628..dcbedb23 100644 Binary files a/tools/rag/vector_store/23bb3f6e-b7de-4238-9726-2e27241977fd/length.bin and b/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/length.bin differ diff --git a/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/link_lists.bin b/tools/rag/vector_store/c8e95a69-7630-42a4-95ec-491a944e40ed/link_lists.bin new file mode 100644 index 00000000..e69de29b diff --git a/tools/rag/vector_store/chroma.sqlite3 b/tools/rag/vector_store/chroma.sqlite3 index 61002f8b..d5312bcc 100644 Binary files a/tools/rag/vector_store/chroma.sqlite3 and b/tools/rag/vector_store/chroma.sqlite3 differ diff --git a/tools/test_validate/README.md b/tools/test_validate/README.md index 1fc98126..ee1c1d76 100644 --- a/tools/test_validate/README.md +++ b/tools/test_validate/README.md @@ -1,57 +1,51 @@ -# Test & Validation Tools (Unified) +# 测试与验证工具 -该目录包含用于测试无人机规划系统、API 接口及 LLM 服务的集成验证工具集。 +该目录用于接口回归、批量稳定性测试和结果导出。 -## 🚀 快速开始 - -使用统一入口脚本启动交互式菜单: +## 快速开始(完整命令) ```bash -python run_tests.py +cd /home/huangfukk/DronePlanning +source backend_service/venv/bin/activate +python tools/test_validate/run_tests.py ``` -## 🛠️ 测试模式 - -### 1. 交互式单次测试 (Mode 1) -- **场景**: 快速验证单条指令,调试 Prompt。 -- **操作**: 在终端输入自然语言指令,即时获取结果。 -- **输出**: `validation/temporary/{指令名}/` - - `response.json`: 完整 API 响应 - - `plan.png`: 可视化任务树 - - `process.log`: 请求与响应日志 - -### 2. 批量/场景测试 (Mode 2) -- **场景**: - - **场景测试**: 验证一组预定义指令的正确性(默认运行 1 次)。 - - **稳定性测试**: 对同一组指令进行高频重复测试(如运行 10 次),检测成功率和延迟抖动。 -- **操作**: - 1. 选择指令文件(位于 `instructions/` 目录)。 - 2. 输入每条指令的运行次数(默认 1)。 -- **输出**: `validation/{时间戳}/` - - `test_summary.csv`: 统计摘要(成功率、平均耗时) - - `test_details.csv`: 每次运行的详细记录 - - `instructions_backup.txt`: 本次测试使用的指令备份 - - `{指令名}/`: 包含所有运行的 `.json` 和 `.png` 产物 - -## 📂 目录结构 +## 目录说明 ```text tools/test_validate/ -├── instructions/ # 指令集文件 (.txt) -├── modules/ # 功能模块 -│ ├── api_client.py # API 客户端核心 -│ ├── interactive_test.py # 交互式测试逻辑 -│ ├── batch_runner.py # 批量测试逻辑 -│ ├── visualizer.py # 可视化工具库 -│ ├── llm_tester.py # LLM 连接测试 -│ └── drone_uploader.py # 任务上传工具 -├── validation/ # 测试产物输出 -│ ├── temporary/ # 交互式测试结果 -│ └── {时间戳}/ # 批量测试结果 -└── run_tests.py # 主程序入口 +├── instructions/ # 指令集(.txt) +├── modules/ # API 客户端、批量执行、可视化等 +├── validation/ # 输出结果目录 +└── run_tests.py # 统一入口 ``` -## 📄 配置文件 +## 使用方式 -- **instructions/validate_instructions.txt**: 默认的预定义场景指令集。 -- 您可以在 `instructions/` 下添加任意 `.txt` 文件,测试时会在菜单中自动列出供选择。 +- Mode 1:交互式单条测试(快速调试) +- Mode 2:批量测试(场景回归/稳定性) + +输出默认在 `tools/test_validate/validation/`,包括: + +- `test_summary.csv` +- `test_details.csv` +- 每条指令的 `response.json` 与 `plan.png` + +## 典型回归流程 + +```bash +cd /home/huangfukk/DronePlanning +source backend_service/venv/bin/activate + +# 先确保后端已启动 +./start_all.sh status + +# 运行测试菜单 +python tools/test_validate/run_tests.py +``` + +## 常见问题 + +- 后端未启动:先执行 `./start_all.sh start` 或 `./start_all_vllm.sh start` +- API 超时:查看 `logs/fastapi.log` 与模型日志 +- 结果目录为空:确认菜单中已实际执行测试轮次 diff --git a/tools/test_validate/__pycache__/run_tests.cpython-313.pyc b/tools/test_validate/__pycache__/run_tests.cpython-313.pyc new file mode 100644 index 00000000..708f2193 Binary files /dev/null and b/tools/test_validate/__pycache__/run_tests.cpython-313.pyc differ diff --git a/tools/test_validate/__pycache__/test_validity.cpython-313.pyc b/tools/test_validate/__pycache__/test_validity.cpython-313.pyc new file mode 100644 index 00000000..0228c642 Binary files /dev/null and b/tools/test_validate/__pycache__/test_validity.cpython-313.pyc differ diff --git a/tools/test_validate/instructions/validate_instructions.txt b/tools/test_validate/instructions/validate_instructions.txt index 7060251c..ec249598 100644 --- a/tools/test_validate/instructions/validate_instructions.txt +++ b/tools/test_validate/instructions/validate_instructions.txt @@ -1,10 +1,10 @@ -无人机当前在地面,到广场查找穿黄色衣服的男子,找到后近距离拍照。 -无人机当前在空中,回到广场,对戴帽子的人进行拍照。 -无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。 -无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。 -无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。 -无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。 -无人机当前在地面,到广场边上的施工区域内,发现未戴安全帽的飞近后拍照。 -无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。 -无人机当前在地面,快速去往东边60米,对身穿白色衣服,头戴帽子正在挟持他人的人进行拍照。 -无人机当前在空中,离黑色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。 \ No newline at end of file +无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。 +无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。 +无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。 +无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。 +无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。 +无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。 +无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。 +无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。 +无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。 +无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。 \ No newline at end of file diff --git a/tools/test_validate/modules/__pycache__/drone_uploader.cpython-313.pyc b/tools/test_validate/modules/__pycache__/drone_uploader.cpython-313.pyc new file mode 100644 index 00000000..009ed9a9 Binary files /dev/null and b/tools/test_validate/modules/__pycache__/drone_uploader.cpython-313.pyc differ diff --git a/tools/test_validate/modules/__pycache__/llm_tester.cpython-313.pyc b/tools/test_validate/modules/__pycache__/llm_tester.cpython-313.pyc new file mode 100644 index 00000000..51563992 Binary files /dev/null and b/tools/test_validate/modules/__pycache__/llm_tester.cpython-313.pyc differ diff --git a/tools/test_validate/validation/20260203224629/instructions_backup.txt b/tools/test_validate/validation/20260203224629/instructions_backup.txt new file mode 100644 index 00000000..ec249598 --- /dev/null +++ b/tools/test_validate/validation/20260203224629/instructions_backup.txt @@ -0,0 +1,10 @@ +无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。 +无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。 +无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。 +无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。 +无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。 +无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。 +无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。 +无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。 +无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。 +无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。 \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/test_details.csv b/tools/test_validate/validation/20260203224629/test_details.csv new file mode 100644 index 00000000..06477161 --- /dev/null +++ b/tools/test_validate/validation/20260203224629/test_details.csv @@ -0,0 +1,11 @@ +instruction,run_id,success,latency,error +无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。,1,True,13.293935775756836, +无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。,1,True,10.266583442687988, +无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。,1,True,11.274949550628662, +无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。,1,True,1.6364917755126953, +无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。,1,True,10.27147102355957, +无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。,1,True,13.89813232421875, +无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。,1,True,3.24064302444458, +无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。,1,True,19.873026847839355, +无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。,1,True,13.099174499511719, +无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。,1,True,8.792617082595825, diff --git a/tools/test_validate/validation/20260203224629/test_summary.csv b/tools/test_validate/validation/20260203224629/test_summary.csv new file mode 100644 index 00000000..5c49f92e --- /dev/null +++ b/tools/test_validate/validation/20260203224629/test_summary.csv @@ -0,0 +1,11 @@ +Instruction,Total Runs,Success Runs,Success Rate,Avg Latency +无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。,1,1,100.0%,13.29s +无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。,1,1,100.0%,10.27s +无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。,1,1,100.0%,11.27s +无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。,1,1,100.0%,1.64s +无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。,1,1,100.0%,10.27s +无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。,1,1,100.0%,13.90s +无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。,1,1,100.0%,3.24s +无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。,1,1,100.0%,19.87s +无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。,1,1,100.0%,13.10s +无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。,1,1,100.0%,8.79s diff --git a/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.json new file mode 100644 index 00000000..db79db6d --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.json @@ -0,0 +1,68 @@ +{ + "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": 0.0, + "y": 0.0, + "z": 10.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "ec05c1a4-720d-4cde-9590-a66f81a72561", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.png b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.png new file mode 100644 index 00000000..9db598be Binary files /dev/null and b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.png differ diff --git a/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.json new file mode 100644 index 00000000..4f94b740 --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.json @@ -0,0 +1,14 @@ +{ + "root": { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "person", + "description": "长头发", + "count": 1 + } + }, + "plan_id": "70eb9449-6857-4a27-a99c-7c6708d12db1", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.png b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.png new file mode 100644 index 00000000..584d9f92 Binary files /dev/null and b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.png differ diff --git a/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.json new file mode 100644 index 00000000..4606716c --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.json @@ -0,0 +1,15 @@ +{ + "root": { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0, + "acceptance_radius": 2.0 + } + }, + "plan_id": "ee762817-34de-406a-a216-f0ec3e0edb6e", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。\n\n输出要求(必须遵守):\n- 只输出一个JSON对象,不要任何解释或多余文本。\n- JSON结构:\n{\"root\":{\"type\":\"action\",\"name\":\"\",\"params\":{...}}}\n- root节点必须是action类型节点,不能是控制流节点。\n\n示例:\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “移动到(120,80,20)” → {\"root\":{\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":120.0,\"y\":80.0,\"z\":20.0,\"acceptance_radius\":2.0}}}\n- “飞机自检” → {\"root\":{\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}}}\n\n—— 可用节点定义——\n```json\n{\n \"actions\": [\n {\"name\":\"takeoff\",\"params\":{\"altitude\":\"float[1,100],默认2\"}},\n {\"name\":\"land\",\"params\":{\"mode\":\"'current'/'home'\"}},\n {\"name\":\"fly_to_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"acceptance_radius\":\"默认2.0\"}},\n {\"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,可选\"}},\n {\"name\":\"move_direction\",\"params\":{\"direction\":\"north/south/east/west/forward/backward/left/right/up/down\",\"distance\":\"[1,10000],缺省持续移动\",\"speed\":\"float,可选\"}},\n {\"name\":\"approach_target\",\"params\":{\"target_class\":\"string,要趋近的目标类别\",\"description\":\"string,可选,目标属性描述\",\"stop_distance\":\"float,期望的最终停止距离\",\"speed\":\"float,可选,期望的逼近速度\"}},\n {\"name\":\"rotate\",\"params\":{\"angle\":\"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\"angular_velocity\":\"rad/s,旋转角速度\"}},\n {\"name\":\"rotate_search\",\"params\":{\"target_class\":\"string,要搜寻的目标类别\",\"description\":\"string,可选,目标属性描述\",\"step_angle\":\"float,可选,每一步旋转的角度\",\"total_rotation\":\"float,可选,总共旋转搜索的角度\"}},\n {\"name\":\"manual_confirmation\",\"params\":{}},\n {\"name\":\"loiter\",\"params\":{\"duration\":\"[1,600]秒/until_condition:可选\"}},\n {\"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\"}},\n {\"name\":\"strike_target\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"battle_damage_assessment\",\"params\":{\"target_class\":\"同object_detect\",\"assessment_time\":\"[5,60],默认15\"}},\n {\"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\"}},\n {\"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\"}},\n {\"name\":\"deliver_payload\",\"params\":{\"payload_type\":\"string\",\"release_altitude\":\"[2,100]默认5\"}},\n {\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic/comprehensive\"}},\n {\"name\":\"return_emergency\",\"params\":{\"reason\":\"string,此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"}},\n {\"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\"}}\n ],\n \"conditions\": [\n {\"name\":\"at_waypoint\",\"params\":{\"x\":\"±10000\",\"y\":\"±10000\",\"z\":\"[1,5000]\",\"tolerance\":\"默认3.0\"}},\n {\"name\":\"object_detected\",\"params\":{\"target_class\":\"同object_detect(必传)\",\"description\":\"可选,目标属性\",\"count\":\"默认1\"}},\n {\"name\":\"target_destroyed\",\"params\":{\"target_class\":\"同object_detect\",\"description\":\"可选,目标属性\",\"confidence\":\"[0.5,1.0]默认0.8\"}},\n {\"name\":\"time_elapsed\",\"params\":{\"duration\":\"[1,2700]秒\"}},\n {\"name\":\"gps_status\",\"params\":{\"min_satellites\":\"int[6,15],必传(如8)\"}}\n ]\n}\n```\n\n—— 参数约束——\n- takeoff.altitude: [1, 100]\n- fly_to_waypoint.z: [1, 5000]\n- fly_to_waypoint.x,y: [-10000, 10000]\n- search_pattern.radius: [5, 1000]\n- move_direction.distance: [1, 10000]\n- 若参考知识提供坐标,必须使用并裁剪到约束范围内\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.png b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.png new file mode 100644 index 00000000..44349152 Binary files /dev/null and b/tools/test_validate/validation/20260203224629/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.png differ diff --git a/tools/test_validate/validation/20260203224629/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.json new file mode 100644 index 00000000..b71864ef --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.json @@ -0,0 +1,63 @@ +{ + "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": "move_direction", + "params": { + "direction": "east", + "distance": 30.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "24122475-d286-4034-b9a5-e7e37aff8564", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.png b/tools/test_validate/validation/20260203224629/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.png new file mode 100644 index 00000000..866e0c54 Binary files /dev/null and b/tools/test_validate/validation/20260203224629/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.png differ diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.json new file mode 100644 index 00000000..03f0280a --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.json @@ -0,0 +1,52 @@ +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "3f212bfa-0915-4bc4-a9b1-59266248c05c", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.png b/tools/test_validate/validation/20260203224629/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.png new file mode 100644 index 00000000..a811ad7f Binary files /dev/null and b/tools/test_validate/validation/20260203224629/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.png differ diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.json new file mode 100644 index 00000000..8db81934 --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.json @@ -0,0 +1,45 @@ +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "ad362dde-0c33-435b-9870-9d29beee9278", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.png b/tools/test_validate/validation/20260203224629/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.png new file mode 100644 index 00000000..4a654bdd Binary files /dev/null and b/tools/test_validate/validation/20260203224629/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.png differ diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.json new file mode 100644 index 00000000..4267f08b --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.json @@ -0,0 +1,74 @@ +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 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": "任务完成" + } + } + ] + }, + "plan_id": "c7382dba-30c3-48b9-b175-ec42cb7f90d8", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.png b/tools/test_validate/validation/20260203224629/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.png new file mode 100644 index 00000000..4737b375 Binary files /dev/null and b/tools/test_validate/validation/20260203224629/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.png differ diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.json new file mode 100644 index 00000000..9563cfc0 --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.json @@ -0,0 +1,47 @@ +{ + "root": { + "type": "Sequence", + "name": "SearchPhotoConfirmReturn", + "children": [ + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "确认后返航" + } + } + ] + }, + "plan_id": "00394bf3-1df8-49aa-917a-4d81acb535ec", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.png b/tools/test_validate/validation/20260203224629/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.png new file mode 100644 index 00000000..d72aa331 Binary files /dev/null and b/tools/test_validate/validation/20260203224629/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.png differ diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.json new file mode 100644 index 00000000..e01e69cd --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.json @@ -0,0 +1,36 @@ +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "stop_distance": 3.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "f31e2045-5987-4aab-8543-31d15fa17704", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.png b/tools/test_validate/validation/20260203224629/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.png new file mode 100644 index 00000000..35ebf526 Binary files /dev/null and b/tools/test_validate/validation/20260203224629/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.png differ diff --git a/tools/test_validate/validation/20260203224629/无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。/1.json b/tools/test_validate/validation/20260203224629/无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。/1.json new file mode 100644 index 00000000..fe891f8b --- /dev/null +++ b/tools/test_validate/validation/20260203224629/无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。/1.json @@ -0,0 +1,3 @@ +{ + "error": "在3次尝试后,仍未能生成一个有效的Pytree。" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/instructions_backup.txt b/tools/test_validate/validation/20260203225625/instructions_backup.txt new file mode 100644 index 00000000..ec249598 --- /dev/null +++ b/tools/test_validate/validation/20260203225625/instructions_backup.txt @@ -0,0 +1,10 @@ +无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。 +无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。 +无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。 +无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。 +无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。 +无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。 +无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。 +无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。 +无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。 +无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。 \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/test_details.csv b/tools/test_validate/validation/20260203225625/test_details.csv new file mode 100644 index 00000000..4d9169ae --- /dev/null +++ b/tools/test_validate/validation/20260203225625/test_details.csv @@ -0,0 +1,11 @@ +instruction,run_id,success,latency,error +无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。,1,True,14.206302165985107, +无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。,1,True,9.870139360427856, +无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。,1,True,11.775025844573975, +无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。,1,True,10.768753051757812, +无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。,1,True,10.050657749176025, +无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。,1,True,13.982521057128906, +无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。,1,True,12.104306697845459, +无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。,1,True,21.10497808456421, +无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。,1,True,11.49827790260315, +无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。,1,True,9.288247346878052, diff --git a/tools/test_validate/validation/20260203225625/test_summary.csv b/tools/test_validate/validation/20260203225625/test_summary.csv new file mode 100644 index 00000000..52f86c8e --- /dev/null +++ b/tools/test_validate/validation/20260203225625/test_summary.csv @@ -0,0 +1,11 @@ +Instruction,Total Runs,Success Runs,Success Rate,Avg Latency +无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。,1,1,100.0%,14.21s +无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。,1,1,100.0%,9.87s +无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。,1,1,100.0%,11.78s +无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。,1,1,100.0%,10.77s +无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。,1,1,100.0%,10.05s +无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。,1,1,100.0%,13.98s +无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。,1,1,100.0%,12.10s +无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。,1,1,100.0%,21.10s +无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。,1,1,100.0%,11.50s +无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。,1,1,100.0%,9.29s diff --git a/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.json new file mode 100644 index 00000000..e23f0722 --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.json @@ -0,0 +1,68 @@ +{ + "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": 0.0, + "y": 0.0, + "z": 10.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "e6dd50ed-5ce0-4032-91fa-bb7b9e28233c", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.png b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.png new file mode 100644 index 00000000..9db598be Binary files /dev/null and b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。/1.png differ diff --git a/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.json new file mode 100644 index 00000000..d95911b0 --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.json @@ -0,0 +1,59 @@ +{ + "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": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "长头发的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "长头发的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "长头发的人", + "track_time": 5.0 + } + } + ] + }, + "plan_id": "02ac6a8d-630b-43d7-8374-679cc07f37ed", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.png b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.png new file mode 100644 index 00000000..e959795b Binary files /dev/null and b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。/1.png differ diff --git a/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.json new file mode 100644 index 00000000..d8e83caf --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.json @@ -0,0 +1,68 @@ +{ + "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": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "未带帽子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "未带帽子" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "未带帽子", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "未带帽子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "dca5568f-e062-4d82-b8db-61ef2bed669a", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.png b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.png new file mode 100644 index 00000000..172bbd21 Binary files /dev/null and b/tools/test_validate/validation/20260203225625/无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。/1.png differ diff --git a/tools/test_validate/validation/20260203225625/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.json new file mode 100644 index 00000000..beb858e7 --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.json @@ -0,0 +1,63 @@ +{ + "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": "move_direction", + "params": { + "direction": "east", + "distance": 30.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "身穿白色衣服,头戴帽子的男子", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "046d1b14-1c37-4143-98c5-5a80a61213a1", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.png b/tools/test_validate/validation/20260203225625/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.png new file mode 100644 index 00000000..866e0c54 Binary files /dev/null and b/tools/test_validate/validation/20260203225625/无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。/1.png differ diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.json new file mode 100644 index 00000000..850874f3 --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.json @@ -0,0 +1,52 @@ +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "ded7065b-453b-4b55-8737-1e00388f6a06", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.png b/tools/test_validate/validation/20260203225625/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.png new file mode 100644 index 00000000..a811ad7f Binary files /dev/null and b/tools/test_validate/validation/20260203225625/无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。/1.png differ diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.json new file mode 100644 index 00000000..e7df2f0a --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.json @@ -0,0 +1,45 @@ +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "戴帽子的人" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "戴帽子的人", + "track_time": 10.0 + } + } + ] + }, + "plan_id": "35f56771-b284-4d58-b738-e404f5aa29a1", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.png b/tools/test_validate/validation/20260203225625/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.png new file mode 100644 index 00000000..4a654bdd Binary files /dev/null and b/tools/test_validate/validation/20260203225625/无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。/1.png differ diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.json new file mode 100644 index 00000000..5fe0d871 --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.json @@ -0,0 +1,74 @@ +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "duration": 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": "任务完成" + } + } + ] + }, + "plan_id": "f54dd51c-b9f5-4a9a-802f-941bba44ca12", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.png b/tools/test_validate/validation/20260203225625/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.png new file mode 100644 index 00000000..4737b375 Binary files /dev/null and b/tools/test_validate/validation/20260203225625/无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。/1.png differ diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.json new file mode 100644 index 00000000..7a38b417 --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.json @@ -0,0 +1,47 @@ +{ + "root": { + "type": "Sequence", + "name": "SearchPhotoConfirmReturn", + "children": [ + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "manual_confirmation", + "params": {} + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "穿红色衣服的人" + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "确认后返航" + } + } + ] + }, + "plan_id": "642ee3ce-45a3-4c7b-b1a4-b323cc0dbf6b", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.png b/tools/test_validate/validation/20260203225625/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.png new file mode 100644 index 00000000..d72aa331 Binary files /dev/null and b/tools/test_validate/validation/20260203225625/无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。/1.png differ diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.json new file mode 100644 index 00000000..879be75b --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.json @@ -0,0 +1,36 @@ +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "stop_distance": 3.0 + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person", + "description": "白色衣服戴帽子的男子", + "track_time": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "7e65d4ad-dd23-49ac-80d9-70b648c17cd4", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.png b/tools/test_validate/validation/20260203225625/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.png new file mode 100644 index 00000000..35ebf526 Binary files /dev/null and b/tools/test_validate/validation/20260203225625/无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。/1.png differ diff --git a/tools/test_validate/validation/20260203225625/无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。/1.json b/tools/test_validate/validation/20260203225625/无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。/1.json new file mode 100644 index 00000000..fe891f8b --- /dev/null +++ b/tools/test_validate/validation/20260203225625/无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。/1.json @@ -0,0 +1,3 @@ +{ + "error": "在3次尝试后,仍未能生成一个有效的Pytree。" +} \ No newline at end of file diff --git a/tools/test_validate/validation/temporary/起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航/plan.png b/tools/test_validate/validation/temporary/起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航/plan.png new file mode 100644 index 00000000..639983a5 Binary files /dev/null and b/tools/test_validate/validation/temporary/起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航/plan.png differ diff --git a/tools/test_validate/validation/temporary/起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航/process.log b/tools/test_validate/validation/temporary/起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航/process.log new file mode 100644 index 00000000..a470a2da --- /dev/null +++ b/tools/test_validate/validation/temporary/起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航/process.log @@ -0,0 +1,4 @@ +Prompt: 起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航 +Status: 200 +Latency: 12.107423305511475 +Response: {"root": {"type": "Sequence", "name": "MainTask", "children": [{"type": "action", "name": "takeoff", "params": {"altitude": 1.0}}, {"type": "action", "name": "move_direction", "params": {"direction": "forward", "distance": 1.0}}, {"type": "action", "name": "rotate_search", "params": {"target_class": "person", "description": "穿迷彩衣服的人"}}, {"type": "condition", "name": "object_detected", "params": {"target_class": "person", "description": "穿迷彩衣服的人"}}, {"type": "action", "name": "approach_target", "params": {"target_class": "person", "description": "穿迷彩衣服的人", "stop_distance": 5.0}}, {"type": "action", "name": "return_emergency", "params": {"reason": "任务完成"}}]}, "plan_id": "fd63525f-41b0-46e4-8405-09278be536ac", "visualization_url": "/static/py_tree.png", "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---"} diff --git a/tools/test_validate/validation/temporary/起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航/response.json b/tools/test_validate/validation/temporary/起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航/response.json new file mode 100644 index 00000000..132f8050 --- /dev/null +++ b/tools/test_validate/validation/temporary/起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航/response.json @@ -0,0 +1,58 @@ +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "takeoff", + "params": { + "altitude": 1.0 + } + }, + { + "type": "action", + "name": "move_direction", + "params": { + "direction": "forward", + "distance": 1.0 + } + }, + { + "type": "action", + "name": "rotate_search", + "params": { + "target_class": "person", + "description": "穿迷彩衣服的人" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person", + "description": "穿迷彩衣服的人" + } + }, + { + "type": "action", + "name": "approach_target", + "params": { + "target_class": "person", + "description": "穿迷彩衣服的人", + "stop_distance": 5.0 + } + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + }, + "plan_id": "fd63525f-41b0-46e4-8405-09278be536ac", + "visualization_url": "/static/py_tree.png", + "final_prompt": "=== System Prompt ===\n任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。\n\n## 一、核心节点定义(格式不可修改,确保后端解析)\n#### 1. 可用节点定义 (必须遵守)\n你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:\n```json\n{\n \"actions\": [\n {\n \"name\": \"takeoff\",\n \"params\": {\n \"altitude\": \"float[1,100],默认2\"\n }\n },\n {\n \"name\": \"land\",\n \"params\": {\n \"mode\": \"'current'/'home'\"\n }\n },\n {\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"acceptance_radius\": \"默认2.0\",\n \"desc\": \"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用\"\n }\n },\n {\n \"name\": \"fly_sequence\",\n \"params\": {\n \"waypoints\": \"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)\",\n \"coordinate_frame\": \"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)\",\n \"speed\": \"float,可选\"\n }\n },\n {\n \"name\": \"move_direction\",\n \"params\": {\n \"direction\": \"north/south/east/west/forward/backward/left/right\",\n \"distance\": \"[1,10000],缺省则持续移动\",\n \"speed\": \"float,可选\",\n \"desc\": \"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点\"\n }\n },\n {\n \"name\": \"approach_target\",\n \"params\": {\n \"target_class\": \"string,要趋近的目标类别\",\n \"description\": \"string,可选,目标属性描述\",\n \"stop_distance\": \"float,期望的最终停止距离\",\n \"speed\": \"float,可选,期望的逼近速度\"\n }\n },\n {\n \"name\": \"rotate\",\n \"params\": {\n \"angle\": \"float,无人机自身旋转角度(正数逆时针,负数顺时针)\",\n \"angular_velocity\": \"rad/s,旋转角速度\"\n }\n },\n {\n \"name\": \"rotate_search\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"string,可选,目标属性描述\",\n \"step_angle\": \"float,可选,每一步旋转的角度\",\n \"total_rotation\": \"float,可选,总共旋转搜索的角度\"\n }\n },\n {\n \"name\": \"manual_confirmation\",\n \"params\": {}\n },\n {\n \"name\": \"loiter\",\n \"params\": {\n \"duration\": \"[1,600]秒/until_condition:可选\"\n }\n },\n {\n \"name\": \"object_detect\",\n \"params\": {\n \"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\",\n \"description\": \"可选,\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"search_pattern\",\n \"params\": {\n \"pattern_type\": \"spiral/grid\",\n \"center_x\": \"±10000\",\n \"center_y\": \"±10000\",\n \"center_z\": \"[1,5000]\",\n \"radius\": \"[5,1000]\",\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n },\n {\n \"name\": \"track_object\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n },\n {\n \"name\": \"deliver_payload\",\n \"params\": {\n \"payload_type\": \"string\",\n \"release_altitude\": \"[2,100]默认5\"\n }\n },\n {\n \"name\": \"system_checks\",\n \"params\": {\n \"check_level\": \"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点\"\n }\n },\n {\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint\"\n }\n },\n {\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"同object_detect\",\n \"description\": \"可选,目标属性\",\n \"track_time\": \"[1,600]秒(必传,不可用'duration')\",\n \"min_confidence\": \"[0.5,1.0]默认0.7\",\n \"safe_distance\": \"[2,50]默认10\"\n }\n }\n ],\n \"conditions\": [\n {\n \"name\": \"at_waypoint\",\n \"params\": {\n \"x\": \"±10000\",\n \"y\": \"±10000\",\n \"z\": \"[1,5000]\",\n \"tolerance\": \"默认3.0\"\n }\n },\n {\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"同object_detect(必传)\",\n \"description\": \"可选,目标属性\",\n \"count\": \"默认1\"\n }\n }\n ],\n \"control_flow\": [\n {\n \"name\": \"Sequence\",\n \"params\": {},\n \"children\": \"子节点数组(按序执行,全成功则成功)\"\n },\n {\n \"name\": \"Selector\",\n \"params\": {\n \"memory\": \"默认true\"\n },\n \"children\": \"子节点数组(执行到成功为止)\"\n },\n {\n \"name\": \"Parallel\",\n \"params\": {\n \"policy\": \"all_success/success_on_one\"\n },\n \"children\": \"子节点数组(同时执行,默认all_success)\"\n }\n ],\n \"decorators\": [\n {\n \"name\": \"SuccessIsFailure\",\n \"params\": {},\n \"child\": \"单一子节点(将子节点的成功结果反转为失败)\"\n }\n ]\n}\n```\n\n## 二、节点必填字段(后端Schema强制要求,缺一验证失败)\n每个节点必须包含以下字段,字段名/类型不可自定义:\n1. **`type`**: \n - 动作节点→`\"action\"`,条件节点→`\"condition\"`,控制流节点→`\"Sequence\"`/`\"Selector\"`/`\"Parallel\"`,装饰器节点→`\"decorator\"`;\n2. **`name`**:必须是上述JSON中定义的`name`值;\n3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;\n4. **`children`**:仅控制流节点必含(子节点数组);\n5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。\n\n## 三、标准任务结构模板(单次起降流程)\n当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, // 接近目标区域\n // --- 核心任务区 (根据指令替换) ---\n // 默认不需要降落节点,除非用户明确要求\n ]\n }\n}\n```\n而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可\n\n## 四、场景示例(请灵活参考)\n\n#### 场景 1:线性搜索任务(Sequence + Selector)\n**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”\n**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。\n**结构**:Sequence (按顺序执行)\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainSearchTask\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}, \n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\n \"type\": \"Selector\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"Sequence\",\n \"name\": \"PhotoIfFound\",\n \"children\": [\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"person\",\"description\":\"扎辫子女子\",\"track_time\":10.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}} // 未发现时的备选动作\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 2:带中断逻辑的巡逻(Selector 示例)\n**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。”\n**参考知识**:航点A的坐标(x:100.0,y:50.0)\n**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"basic\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\n \"type\": \"Selector\",\n \"name\": \"FlyOrDetect\",\n \"children\": [\n {\n \"type\": \"Sequence\", \n \"name\": \"InterruptionLogic\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"object_detect\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"person\"}},\n {\"type\":\"action\",\"name\":\"loiter\",\"params\":{\"duration\":5.0}}\n ]\n },\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":100.0,\"y\":50.0,\"z\":10.0}}\n ]\n }\n ]\n }\n}\n```\n\n#### 场景 3:长期监控任务(Parallel 示例)\n**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。”\n**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0)\n**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。\n使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。\n**结构**:\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"MainTask\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"fly_to_waypoint\",\n \"params\": {\n \"x\": 50.0,\n \"y\": 50.0,\n \"z\": 10.0\n }\n },\n {\n \"type\": \"Parallel\",\n \"name\": \"MonitorAndPhoto\",\n \"params\": {\n \"policy\": \"success_on_one\"\n },\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"loiter\",\n \"params\": {\n \"time\": 300\n }\n },\n {\n \"type\": \"decorator\",\n \"name\": \"SuccessIsFailure\",\n \"child\": {\n \"type\": \"Sequence\",\n \"name\": \"CheckAndPhoto\",\n \"children\": [\n {\n \"type\": \"action\",\n \"name\": \"object_detect\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"condition\",\n \"name\": \"object_detected\",\n \"params\": {\n \"target_class\": \"person\"\n }\n },\n {\n \"type\": \"action\",\n \"name\": \"take_photos\",\n \"params\": {\n \"target_class\": \"person\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"type\": \"action\",\n \"name\": \"return_emergency\",\n \"params\": {\n \"reason\": \"任务完成\"\n }\n }\n ]\n }\n}\n```\n\n#### 场景 4:交互式确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”\n**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchConfirmPhoto\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}}\n ]\n }\n}\n```\n\n#### 场景 5:后置确认任务(Sequence + Manual Confirmation)\n**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”\n**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"SearchPhotoConfirmReturn\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"system_checks\",\"params\":{\"check_level\":\"comprehensive\"}},\n {\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"take_photos\",\"params\":{\"target_class\":\"car\",\"description\":\"小汽车\"}},\n {\"type\":\"action\",\"name\":\"manual_confirmation\",\"params\":{}},\n {\"type\":\"action\",\"name\":\"return_emergency\",\"params\":{\"reason\":\"确认后返航\"}}\n ]\n }\n}\n```\n\n#### 场景 6:移动后条件降落(Sequence)\n**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”\n**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0)\n**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。\n**结构**:Sequence\n```json\n{\n \"root\": {\n \"type\": \"Sequence\",\n \"name\": \"FlySearchLand\",\n \"children\": [\n {\"type\":\"action\",\"name\":\"fly_to_waypoint\",\"params\":{\"x\":0.0,\"y\":0.0,\"z\":10.0}},\n {\"type\":\"action\",\"name\":\"rotate_search\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"condition\",\"name\":\"object_detected\",\"params\":{\"target_class\":\"traffic_light\",\"description\":\"红绿灯\"}},\n {\"type\":\"action\",\"name\":\"land\",\"params\":{\"mode\":\"current\"}}\n ]\n }\n}\n```\n\n## 六、高频错误规避\n1. 控制流节点的 `type` 必须是 `\"Sequence\"`, `\"Selector\"` 或 `\"Parallel\"`\n2. **人工确认节点 (`manual_confirmation`) 使用原则**:\n - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。\n - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。\n3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。\n4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点\n5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点\n6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点\n7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。\n8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。\n9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint\n10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。\n\n示例(必须遵守):\n- “无人机当前在地面,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n- “无人机当前在空中,快速去往东边60米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。” → 同样必须使用 `move_direction`(east, 60),不得生成 `fly_to_waypoint` 或引用任何地点坐标。\n\n## 七、坐标计算规则(东南天坐标系 ENU)\n本系统统一使用东南天(ENU)坐标系:\n- **X轴**:正方向为**东** (East),负方向为**西** (West)\n- **Y轴**:正方向为**南** (South)向为**北** (North)\n- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)\n\n**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:**\n\n当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:\n- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction`: east/west/north/south/up/down\n - `distance`: 偏移距离(米)\n- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:\n - `base`: 参考地点的ENU坐标(含x/y/z)\n - `direction_text`: 中文方位原文(如“东南”“南偏东10度”)\n - `distance`: 偏移距离(米)\n- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。\n\n示例(必须遵守):\n- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"东南\"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。\n- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `\"南偏东10度\"`,`distance` 为 `100`。\n\n当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。\n当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。\n\n## 八、输出要求\n仅输出1个严格符合上述所有规则的JSON对象。\n\n【强制规则】仅生成包含root的复杂行为树JSON,不得输出简单模式(不得包含mode字段或仅有action节点)。\n\n=== User Prompt ===\n起飞到1米,向前飞1米,寻找穿迷彩衣服的人,找到后靠近,然后返航\n\n---\n参考知识:\n以下是从知识库中检索到的、与当前任务最相关的信息,请优先参考这些信息来生成结果:\n{\"property\": \"rules\", \"information\": {\"name\": \"紧急返航\", \"description\": \"当指令中要求紧急返航到某地时,必须使用fly_to_waypoint节点,严禁使用return_emergency节点。\"}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"飞行场地\", \"coordinates\": {\"x\": 0, \"y\": 0, \"z\": 0}}}\n\n{\"property\": \"location\", \"information\": {\"name\": \"大楼外围四个点东南天坐标系坐标\", \"coordinates\": {\"A\": {\"x\": -24.0, \"y\": 241.8, \"z\": 0}, \"B\": {\"x\": -108.5, \"y\": 241.8, \"z\": 0}, \"C\": {\"x\": -108.5, \"y\": 289.8, \"z\": 0}, \"D\": {\"x\": -24.0, \"y\": 292.8, \"z\": 0}}}}\n---" +} \ No newline at end of file diff --git a/tools/vllm_templates/qwen3_xml_tool.jinja b/tools/vllm_templates/qwen3_xml_tool.jinja new file mode 100644 index 00000000..973fa387 --- /dev/null +++ b/tools/vllm_templates/qwen3_xml_tool.jinja @@ -0,0 +1,54 @@ +{%- if tools %} +<|im_start|>system +{%- if messages[0].role == 'system' -%} +{{ messages[0].content }} +{%- else -%} +You are a helpful assistant. +{%- endif -%} + +# Tools +You may call one or more functions to assist with the user query. +The function signatures are provided below: + +{%- for tool in tools %} +{{ tool | tojson }} +{%- endfor %} + + +When you need to call a tool, respond with XML tags in this exact format: + + +value1 +value2 + + +<|im_end|> +{%- else %} +{%- if messages[0].role == 'system' -%} +<|im_start|>system +{{ messages[0].content }}<|im_end|> +{%- endif -%} +{%- endif %} + +{%- for message in messages %} +{%- if (message.role == "user") or (message.role == "system" and not loop.first) %} +<|im_start|>{{ message.role }} +{{ message.content }}<|im_end|> +{%- elif message.role == "assistant" %} +<|im_start|>assistant +{%- if message.content %} +{{ message.content }} +{%- endif %} +<|im_end|> +{%- elif message.role == "tool" %} +<|im_start|>user + +{{ message.content }} + +<|im_end|> +{%- endif %} +{%- endfor %} + +{%- if add_generation_prompt %} +<|im_start|>assistant +{%- endif %}