2 Commits

Author SHA1 Message Date
fa5e5e7c23 chore: 添加 .gitignore,从版本控制中移除 logs、vector_store、测试结果、生成图片
Made-with: Cursor
2026-02-26 20:02:16 +08:00
c7f6a0da17 流程节点完善 2026-02-26 19:37:55 +08:00
3035 changed files with 3032 additions and 72944 deletions

34
.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# ============ 日志 ============
logs/
# ============ 向量库RAG 检索用) ============
**/vector_store/
vector_store/
# ============ 测试脚本的测试结果 ============
tools/test_validate/validation/
# ============ 生成的图片 ============
backend_service/generated_visualizations/
tools/test_validate/validation/**/*.png
tools/test_validate/validation/**/*.jpg
# ============ Python ============
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
.venv/
env/
.env/
# ============ 其他常见忽略 ============
*.log
.DS_Store
.idea/
.vscode/
*.swp
*.swo
*~

View File

@@ -12,9 +12,13 @@ api[main.py /generate_plan] --> gen[py_tree_generator.generate]
gen --> orch[GenerationOrchestrator] gen --> orch[GenerationOrchestrator]
orch --> s1[Stage1 TaskUnderstanding] orch --> s1[Stage1 TaskUnderstanding]
orch --> s2[Stage2 ContextBinding] orch --> s2[Stage2 ContextBinding]
orch --> s3[Stage3 BTPlanning] orch --> s3[Stage3 MacroPlanning]
orch --> s4[Stage4 ValidateAndPostprocess] s3 --> simple{simple?}
s4 --> out[返回 py_tree JSON] simple -->|是| s6[Stage6 Validate]
simple -->|否| s4[Stage4 Middleware]
s4 --> s5[Stage5 MicroFilling]
s5 --> s6
s6 --> out[返回 py_tree JSON]
``` ```
对应代码: 对应代码:
@@ -24,6 +28,65 @@ s4 --> out[返回 py_tree JSON]
- `backend_service/src/pipeline/orchestrator.py` - `backend_service/src/pipeline/orchestrator.py`
- `backend_service/src/pipeline/stages.py` - `backend_service/src/pipeline/stages.py`
### 1.1 各 Stage 提示词模板一览
| Stage | 是否调用 LLM | System 模板/片段(按组合顺序) | User 内容 |
|-------|----------------|--------------------------------|------------|
| Stage1 | 是 | `prompts/scene_classifier_prompt.txt` | 原始 `user_prompt` |
| Stage2 | 否 | — | — |
| Stage3 | 是 | **simple**`simple_mode_prompt.txt`**complex**`macro_header.txt` → 裁剪的 `core_nodes.json``template_ground.txt` / `template_air.txt``common_rules.txt` → 可选 `system_extra_examples.txt` → 意图标签 | `user_prompt` + 参考知识(地点/模式/规则) |
| Stage4 | 否 | — | — |
| Stage5 | 是 | `micro_header.txt` → macro_tree JSON → resolved_data JSON → 裁剪的 `atomic/nodes_schema.json` | 固定句:「请直接输出完整的带有 params 参数的 JSON 树结构。」 |
| Stage6 | 否 | — | — |
各阶段模板的详细组合顺序与条件见对应小节(如 2.3、3.3、4.3、5.3、6.3、7.3)。
### 1.2 提示词分配流程图
下图按 Stage 标出各阶段使用的提示词模板及组合关系(仅含涉及 LLM 的 Stage
```mermaid
flowchart TB
subgraph S1["Stage1 任务理解"]
direction TB
P1_sys["system: scene_classifier_prompt.txt"]
P1_usr["user: user_prompt"]
end
subgraph S2["Stage2 上下文绑定"]
P2["无 LLM / 无提示词"]
end
subgraph S3["Stage3 宏观规划"]
direction TB
P3a["simple: system = simple_mode_prompt.txt"]
P3b["complex: system = macro_header → core_nodes → template_ground/air → common_rules → 可选 system_extra_examples → 意图标签"]
P3_usr["user: user_prompt + 参考知识"]
end
subgraph S4["Stage4 中间层解析"]
P4["无 LLM / 无提示词"]
end
subgraph S5["Stage5 微观填参"]
direction TB
P5_sys["system: micro_header → macro_tree JSON → resolved_data JSON → atomic/nodes_schema 裁剪"]
P5_usr["user: 固定句"]
end
subgraph S6["Stage6 校验与后处理"]
P6["无 LLM / 无提示词"]
end
S1 --> S2 --> S3
S3 --> S4 --> S5 --> S6
```
- **Stage1 / Stage3 / Stage5** 会调用 LLM其 System/User 内容由上图对应框内模板或片段组合而成。
- **Stage2 / Stage4 / Stage6** 不调用 LLM无提示词分配。
---
## 2. Stage1任务理解TaskUnderstanding ## 2. Stage1任务理解TaskUnderstanding
功能: 功能:
@@ -80,6 +143,15 @@ s4 --> out[返回 py_tree JSON]
- `risk_flags`: 风险标记列表(如 `needs_manual_confirmation` - `risk_flags`: 风险标记列表(如 `needs_manual_confirmation`
- `constraints`: 约束占位(当前为空对象) - `constraints`: 约束占位(当前为空对象)
### 2.3 本阶段组合的提示词模板
| 角色 | 模板文件 | 路径 | 说明 |
|--------|----------|------|------|
| system | 场景分类 | `prompts/scene_classifier_prompt.txt` | 唯一 system 提示词,定义 simple/scene1/scene4 判定规则与示例 |
| user | 用户原文 | 调用方传入的 `user_prompt` | 不做拼接,直接作为 user 消息 |
组合方式:`messages = [ { "role": "system", "content": scene_classifier_prompt }, { "role": "user", "content": user_prompt } ]`。分类结果解析为 `scene_mode`,其余 `intent_type``risk_flags` 由本阶段规则函数从 `user_prompt` 推断,不读模板。
## 3. Stage2上下文绑定ContextBinding ## 3. Stage2上下文绑定ContextBinding
功能: 功能:
@@ -163,29 +235,32 @@ s4 --> out[返回 py_tree JSON]
- `relative_refs`: 相对目标结构化描述 - `relative_refs`: 相对目标结构化描述
- `required_actions`: 后续用于节点裁剪注入的动作白名单 - `required_actions`: 后续用于节点裁剪注入的动作白名单
## 4. Stage3BT 生成BTPlanning ### 3.3 本阶段组合的提示词模板
本阶段**不调用 LLM**无提示词模板。仅做检索Location/Pattern/Rules、规则推导`required_actions``relative_refs`)、预计算航点。
---
## 4. Stage3宏观规划Macro PlanningRound 1
功能: 功能:
- 组装 prompt骨架 + 节点裁剪 + 示例 + 规则 + 检索结果) - 组装 prompt骨架 + 节点裁剪 + 模板 + 规则 + 检索结果)
- 调用对应模型生成严格 JSON - 调用对应模型生成宏观树(及可选的 parameter_requests
- 解析模型响应(含 reasoning 提取) - 解析模型响应(含 reasoning 提取)
核心代码: 核心代码:
- `backend_service/src/prompting/composer.py::PromptComposer` - `backend_service/src/prompting/composer.py::PromptComposer.compose_macro()`
- `backend_service/src/llm/gateway.py::generate_json()` - `backend_service/src/llm/gateway.py::generate_json()`
- `backend_service/src/llm/response_parser.py` - `backend_service/src/pipeline/stages.py::stage3_macro_planning()`
- `backend_service/src/pipeline/stages.py::stage3_bt_planning()`
- 数据契约:`backend_service/src/pipeline/contracts.py::BTDraft` - 数据契约:`backend_service/src/pipeline/contracts.py::BTDraft`
关键行为: 关键行为:
- simple 模式与复杂模式使用不同客户端/模型配置 - simple 模式与复杂模式使用不同客户端/模型配置Stage3 强制关闭 thinking强制 `response_format=json_object`;节点定义采用裁剪注入(非全量注入)。
- Stage3 强制关闭 thinking强制 `response_format=json_object`
- 节点定义采用裁剪注入(非全量注入)
### 4.1 输入格式 ### 4.1 输入格式Stage3
该阶段接收: 该阶段接收:
@@ -244,7 +319,82 @@ s4 --> out[返回 py_tree JSON]
- `reasoning_text`: 可选推理文本(若模型返回) - `reasoning_text`: 可选推理文本(若模型返回)
- `final_prompt`: 完整组合记录(便于离线排查) - `final_prompt`: 完整组合记录(便于离线排查)
## 5. Stage4校验与后处理ValidateAndPostprocess ### 4.3 本阶段组合的提示词模板
**simple 模式**(单轮,直接出最终树):
| 角色 | 模板/内容 | 路径 | 说明 |
|--------|------------|------|------|
| system | 简单模式全文 | `prompts/simple_mode_prompt.txt` | 直接作为 system无拼接 |
| user | 用户原文 + 参考知识 | 动态 | `user_prompt` + `_build_user_augmentation(context_blocks)` |
**复杂模式**scene1/scene4宏观树 Round 1
System 按**顺序**拼接以下内容(来自 `prompting/composer.py::compose_macro()`
| 顺序 | 模板/内容 | 路径 | 说明 |
|------|-----------|------|------|
| 1 | 宏观任务头 | `prompts/partials/macro_header.txt` | 任务定义与输出要求 |
| 2 | 节点定义(裁剪后) | `prompts/partials/core_nodes.json` | 按 `required_actions` + `risk_flags` 裁剪,最多 30 个 action/condition格式化为「## 一、核心节点定义」+ JSON 代码块 |
| 3 | 任务模板(二选一) | `prompts/partials/template_ground.txt``prompts/partials/template_air.txt` | 由 `drone_state`on_ground / in_air决定 |
| 4 | 通用规则 | `prompts/partials/common_rules.txt` | 若文件存在则追加 |
| 5 | 额外示例(可选) | `prompts/partials/system_extra_examples.txt` | 仅当 `intent_type == "generic_mission"` 时追加 |
| 6 | 意图标签 | 代码生成 | 固定段落:`## 任务意图标签\n- intent_type: \`{intent_type}\`` |
User 消息:
- 内容 = `user_prompt` + 参考知识增强段。
- 参考知识增强段由 `_build_user_augmentation(context_blocks)` 生成:若 `context_blocks` 中 `location` / `pattern` / `rules` 非空,则按顺序拼接为「【地点知识】…」「【任务模式】…」「【规则知识】…」,整体包在 `---\n参考知识\n…\n---` 中。
---
## 5. Stage4中间层解析Middleware Resolution
功能:
- 读取 Stage3 输出的 `parameter_requests`
- 对含 `landmark` 等实体的请求做位置检索与航点预计算,写入 `resolved_data`
- 其他实体透传为 `{node}_entities`,供 Stage5 使用
核心代码:
- `backend_service/src/pipeline/stages.py::stage4_middleware_resolution()`
- 复用 `retriever.retrieve(scopes=["location"])` 与 `tool_runtime.build_precomputed_waypoint()`
### 5.3 本阶段组合的提示词模板
本阶段**不调用 LLM**,无提示词模板。仅做依赖解析与数据绑定。
---
## 6. Stage5微观参数填空Micro Parameter FillingRound 2
功能:
- 从 `prompts/atomic/nodes_schema.json` 按宏观树中用到的节点名裁剪出 `atomic_schema`
- 调用 `PromptComposer.compose_micro()` 组装 Round 2 的 system 提示词
- 再次调用模型,输出带完整 `params` 的 JSON 树
核心代码:
- `backend_service/src/prompting/composer.py::compose_micro()`
- `backend_service/src/pipeline/stages.py::stage5_micro_filling()`
### 6.3 本阶段组合的提示词模板
| 角色 | 模板/内容 | 路径 | 说明 |
|--------|------------|------|------|
| system | 微观任务头 | `prompts/partials/micro_header.txt` | 第一段 |
| system | 宏观骨架树 | 运行时 | `## 1. 原宏观骨架树 (macro_tree)` + `draft.macro_tree` 的 JSON |
| system | 确切数据字典 | 运行时 | `## 2. 确切数据字典 (resolved_data)` + Stage4 输出的 `resolved_data` JSON |
| system | 原子节点规范 | `prompts/atomic/nodes_schema.json`(按需裁剪) | `## 3. 原子节点规范 (atomic_schema)`;仅保留宏观树中出现的 action/condition 的 schema |
| user | 固定指令 | 代码写死 | `"请直接输出完整的带有 params 参数的 JSON 树结构。"` |
组合方式system = 上述四段用 `\n\n` 拼接user = 固定字符串。Round 2 不再注入 RAG 检索块。
---
## 7. Stage6校验与后处理ValidateAndPostprocess
功能: 功能:
@@ -257,11 +407,11 @@ s4 --> out[返回 py_tree JSON]
- `backend_service/src/validation/validator.py` - `backend_service/src/validation/validator.py`
- `backend_service/src/validation/schema_provider.py` - `backend_service/src/validation/schema_provider.py`
- `backend_service/src/pipeline/stages.py::stage4_validate_and_postprocess()` - `backend_service/src/pipeline/stages.py::stage6_validate_and_postprocess()`
- `backend_service/src/py_tree_generator.py::render_visualization()` - `backend_service/src/py_tree_generator.py::render_visualization()`
- `backend_service/src/py_tree_generator.py::_save_history()` - `backend_service/src/py_tree_generator.py::_save_history()`
### 5.1 输入格式 ### 7.1 输入格式
该阶段接收: 该阶段接收:
@@ -269,10 +419,9 @@ s4 --> out[返回 py_tree JSON]
- `TaskUnderstanding` - `TaskUnderstanding`
- `ContextBinding` - `ContextBinding`
- `BTDraft` - `BTDraft`
- 复杂模式下还有 Stage5 的 `final_tree`simple 模式下为 `draft.llm_raw_json`
其中主载荷来自 `BTDraft.llm_raw_json` ### 7.2 输出格式(最终 API 返回)
### 5.2 输出格式(最终 API 返回)
复杂模式示例: 复杂模式示例:
@@ -326,7 +475,13 @@ simple 模式示例:
- `visualization_url`: 最新可视化图访问路径 - `visualization_url`: 最新可视化图访问路径
- `final_prompt`: 生成时使用的完整提示词记录 - `final_prompt`: 生成时使用的完整提示词记录
## 6. 数据入库RAG Ingestion逻辑 ### 7.3 本阶段组合的提示词模板
本阶段**不调用 LLM**,无提示词模板。仅做校验、注入元数据与写盘。
---
## 8. 数据入库RAG Ingestion逻辑
入库脚本: 入库脚本:
@@ -340,9 +495,9 @@ simple 模式示例:
- `drone_docs`(兼容) - `drone_docs`(兼容)
- `location_kb` / `pattern_kb` / `rules_kb`(新检索路径) - `location_kb` / `pattern_kb` / `rules_kb`(新检索路径)
## 7. 可自定义修改点(推荐按优先级) ## 9. 可自定义修改点(推荐按优先级)
### 7.1 场景与意图逻辑 ### 9.1 场景与意图逻辑
可改文件: 可改文件:
@@ -354,7 +509,7 @@ simple 模式示例:
- `_extract_risk_flags()`:新增风险规则 - `_extract_risk_flags()`:新增风险规则
- `_derive_required_actions()`:调整规则推导的动作集合 - `_derive_required_actions()`:调整规则推导的动作集合
### 7.2 Prompt 策略 ### 9.2 Prompt 策略
可改文件: 可改文件:
@@ -369,7 +524,7 @@ simple 模式示例:
- 示例注入策略(何时注入 extra examples - 示例注入策略(何时注入 extra examples
- 用户侧检索增强格式 - 用户侧检索增强格式
### 7.3 模型路由与推理参数 ### 9.3 模型路由与推理参数
可改文件: 可改文件:
@@ -381,7 +536,7 @@ simple 模式示例:
- `temperature`、`max_tokens`、重试次数 - `temperature`、`max_tokens`、重试次数
- thinking 开关策略Stage1/Stage3 - thinking 开关策略Stage1/Stage3
### 7.4 检索策略 ### 9.4 检索策略
可改文件: 可改文件:
@@ -396,7 +551,7 @@ simple 模式示例:
- kb_type 划分方式 - kb_type 划分方式
- 文档切分与 metadata 设计 - 文档切分与 metadata 设计
### 7.5 相对目标解析 ### 9.5 相对目标解析
可改文件: 可改文件:
@@ -409,7 +564,7 @@ simple 模式示例:
- 静态解析能力(何时生成 `resolved_refs` - 静态解析能力(何时生成 `resolved_refs`
- 与 UAV 端协议字段兼容策略 - 与 UAV 端协议字段兼容策略
### 7.6 校验与输出协议 ### 9.6 校验与输出协议
可改文件: 可改文件:
@@ -423,7 +578,7 @@ simple 模式示例:
- 顶层 `context` 字段的可选校验 - 顶层 `context` 字段的可选校验
- 失败错误信息与恢复策略 - 失败错误信息与恢复策略
## 8. 关键环境变量 ## 10. 关键环境变量
- `ORIN_IP` - `ORIN_IP`
- `OPENAI_API_KEY` - `OPENAI_API_KEY`
@@ -433,14 +588,14 @@ simple 模式示例:
- `ENABLE_REASONING_CAPTURE` - `ENABLE_REASONING_CAPTURE`
- `REASONING_PREVIEW_LINES` - `REASONING_PREVIEW_LINES`
## 9. 自定义改造建议(实践顺序) ## 11. 自定义改造建议(实践顺序)
1. 先改 Stage1 规则推导(低风险,收益快) 1. 先改 Stage1 规则推导(低风险,收益快)
2. 再改 PromptComposer 的裁剪与注入(控制长度与稳定性) 2. 再改 PromptComposer 的裁剪与注入(控制长度与稳定性)
3. 再改检索策略top_k、回退、metadata 3. 再改检索策略top_k、回退、metadata
4. 最后改 schema 与输出协议(需联动执行端) 4. 最后改 schema 与输出协议(需联动执行端)
## 10. 变更后最小回归清单 ## 12. 变更后最小回归清单
每次改造后至少验证: 每次改造后至少验证:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
{
"timestamp": "2026-02-24T12:33:02.890173",
"request": "无人机当前在地面,起飞",
"response": {
"root": {
"type": "action",
"name": "takeoff",
"params": {
"altitude": 10.0
}
},
"plan_id": "aad1912a-c27d-4abd-bf62-3179fd61d6aa",
"visualization_url": "/static/py_tree.png",
"final_prompt": "=== System Prompt (Macro) ===\n你是一个无人机简单指令执行规划器。\n假设输入一定是“单一原子动作即可完成”的简单指令。你的任务是输出一个严格的JSON对象。\n\n输出要求必须遵守\n- 只输出一个JSON对象不要任何解释或多余文本。\n- JSON结构固定为\n{\"root\":{\"type\":\"action\",\"name\":\"<action_name>\",\"params\":{...}}}\n- root 节点必须是 action禁止输出 Sequence/Selector/Parallel 等控制流节点。\n- params 只能包含该动作定义内的字段,禁止自定义字段。\n- 数值请使用数字类型(例如 10.0)。\n\n可用动作simple 模式只允许从下列动作中选择其一):\n1) takeoff: {\"altitude\": float[1,100]}(仅当地面起飞)\n2) land: {\"mode\": \"current\"|\"home\"}\n3) fly_to_waypoint: {\"x\": number, \"y\": number, \"z\": number, \"acceptance_radius\": number(可选)}\n4) move_direction: {\"direction\": \"north\"|\"south\"|\"east\"|\"west\"|\"forward\"|\"backward\"|\"left\"|\"right\"|\"up\"|\"down\", \"distance\": number}\n5) rotate: {\"angle\": number, \"angular_velocity\": number(可选)}\n6) loiter: {\"duration\": number}\n7) system_checks: {\"check_level\":\"basic\"|\"comprehensive\"}(仅当指令明确在地面且用户要求自检)\n\n示例\n- “起飞到10米” → {\"root\":{\"type\":\"action\",\"name\":\"takeoff\",\"params\":{\"altitude\":10.0}}}\n- “往北飞50米” → {\"root\":{\"type\":\"action\",\"name\":\"move_direction\",\"params\":{\"direction\":\"north\",\"distance\":50.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\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---"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@ import logging
# import threading # ROS2相关已注释 # import threading # ROS2相关已注释
# import rclpy # ROS2相关已注释 # import rclpy # ROS2相关已注释
from .models import GeneratePlanRequest, ExecuteMissionRequest from .models import GeneratePlanRequest, ExecuteMissionRequest, DebugStageRequest
from .websocket_manager import websocket_manager from .websocket_manager import websocket_manager
from .py_tree_generator import py_tree_generator from .py_tree_generator import py_tree_generator
# from .ros2_client import MissionActionClient # ROS2相关已注释 # from .ros2_client import MissionActionClient # ROS2相关已注释
@@ -41,11 +41,29 @@ async def generate_plan_endpoint(request: GeneratePlanRequest):
Receives a user prompt and returns a generated `py_tree.json` with a visualization URL. Receives a user prompt and returns a generated `py_tree.json` with a visualization URL.
""" """
try: try:
pytree_dict = await py_tree_generator.generate(request.user_prompt) pytree_dict = await py_tree_generator.generate(request.user_prompt, drone_state=request.drone_state)
return pytree_dict return pytree_dict
except RuntimeError as e: except RuntimeError as e:
return {"error": str(e)} return {"error": str(e)}
@app.post("/debug_stage", response_model=dict)
async def debug_stage_endpoint(request: DebugStageRequest):
"""
Stage 分阶段调试:运行到指定 stage 并返回该 stage 的输出。
target_stage: 1=TaskUnderstanding, 2=ContextBinding, 3=BTDraft, 4=MiddlewareResolution, 5=MicroFilling, 6=ValidateAndPostprocess
"""
try:
result = py_tree_generator.run_debug_stage(
user_prompt=request.user_prompt,
drone_state=request.drone_state,
target_stage=request.target_stage,
)
return result
except Exception as e:
logging.exception("debug_stage 执行异常")
return {"error": str(e)}
@app.post("/execute_mission", response_model=dict) @app.post("/execute_mission", response_model=dict)
async def execute_mission_endpoint(request: ExecuteMissionRequest): async def execute_mission_endpoint(request: ExecuteMissionRequest):
""" """

View File

@@ -3,6 +3,7 @@ from typing import Dict, Any
class GeneratePlanRequest(BaseModel): class GeneratePlanRequest(BaseModel):
user_prompt: str user_prompt: str
drone_state: str = "on_ground" # "on_ground" or "in_air"
class ExecuteMissionRequest(BaseModel): class ExecuteMissionRequest(BaseModel):
py_tree: Dict[str, Any] py_tree: Dict[str, Any]
@@ -10,3 +11,10 @@ class ExecuteMissionRequest(BaseModel):
class StatusUpdate(BaseModel): class StatusUpdate(BaseModel):
node_id: str node_id: str
status: int status: int
class DebugStageRequest(BaseModel):
"""Stage 分阶段调试请求"""
user_prompt: str
drone_state: str = "on_ground"
target_stage: int = 1 # 1-6

View File

@@ -6,10 +6,12 @@ from pydantic import BaseModel, Field
SceneMode = Literal["simple", "scene1", "scene4"] SceneMode = Literal["simple", "scene1", "scene4"]
DroneState = Literal["on_ground", "in_air"]
class TaskUnderstanding(BaseModel): class TaskUnderstanding(BaseModel):
scene_mode: SceneMode = "scene1" scene_mode: SceneMode = "scene1"
drone_state: DroneState = "on_ground"
intent_type: str = "generic_mission" intent_type: str = "generic_mission"
requires_relative_target: bool = False requires_relative_target: bool = False
entities: Dict[str, Any] = Field(default_factory=dict) entities: Dict[str, Any] = Field(default_factory=dict)
@@ -33,6 +35,7 @@ class BTDraft(BaseModel):
user_prompt: str user_prompt: str
allowed_nodes: Dict[str, List[str]] = Field(default_factory=dict) allowed_nodes: Dict[str, List[str]] = Field(default_factory=dict)
llm_raw_json: Dict[str, Any] llm_raw_json: Dict[str, Any]
macro_tree: Dict[str, Any]
parameter_requests: List[Dict[str, Any]]
reasoning_text: Optional[str] = None reasoning_text: Optional[str] = None
final_prompt: str final_prompt: str

View File

@@ -12,6 +12,20 @@ class GenerationOrchestrator:
async def generate(self, user_prompt: str) -> Dict: async def generate(self, user_prompt: str) -> Dict:
understanding = self.stages.stage1_task_understanding(user_prompt) understanding = self.stages.stage1_task_understanding(user_prompt)
context = self.stages.stage2_context_binding(user_prompt, understanding) 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) if understanding.scene_mode == "simple":
draft = self.stages.stage3_macro_planning(user_prompt, understanding, context)
return self.stages.stage6_validate_and_postprocess(user_prompt, understanding, context, draft, draft.llm_raw_json)
# Round 1: 宏观规划
draft = self.stages.stage3_macro_planning(user_prompt, understanding, context)
# Middleware: 动态依赖解析
resolved_data = self.stages.stage4_middleware_resolution(draft)
# Round 2: 微观填参
final_tree = self.stages.stage5_micro_filling(draft, resolved_data, understanding)
# 验证与后处理
return self.stages.stage6_validate_and_postprocess(user_prompt, understanding, context, draft, final_tree)

View File

@@ -72,13 +72,24 @@ class PipelineStages:
self.generator = generator self.generator = generator
def stage1_task_understanding(self, user_prompt: str) -> TaskUnderstanding: def stage1_task_understanding(self, user_prompt: str) -> TaskUnderstanding:
logging.info("========== [Stage 1] Task Understanding ==========")
scene_mode = self.generator.llm_gateway.classify_scene(user_prompt) scene_mode = self.generator.llm_gateway.classify_scene(user_prompt)
intent_type = _infer_intent_type(user_prompt, scene_mode) intent_type = _infer_intent_type(user_prompt, scene_mode)
risk_flags = _extract_risk_flags(user_prompt) risk_flags = _extract_risk_flags(user_prompt)
requires_relative = "relative_reference_detected" in risk_flags requires_relative = "relative_reference_detected" in risk_flags
# 简单提取状态
drone_state: DroneState = "on_ground"
if "在空中" in user_prompt or "已起飞" in user_prompt:
drone_state = "in_air"
entities = {"raw_prompt": user_prompt} entities = {"raw_prompt": user_prompt}
logging.info(f"Task Understanding Results: mode={scene_mode}, state={drone_state}, intent={intent_type}, risks={risk_flags}")
return TaskUnderstanding( return TaskUnderstanding(
scene_mode=scene_mode, scene_mode=scene_mode,
drone_state=drone_state,
intent_type=intent_type, intent_type=intent_type,
requires_relative_target=requires_relative, requires_relative_target=requires_relative,
entities=entities, entities=entities,
@@ -87,6 +98,7 @@ class PipelineStages:
) )
def stage2_context_binding(self, user_prompt: str, understanding: TaskUnderstanding) -> ContextBinding: def stage2_context_binding(self, user_prompt: str, understanding: TaskUnderstanding) -> ContextBinding:
logging.info("========== [Stage 2] Context Binding ==========")
scopes = ["location"] scopes = ["location"]
if understanding.scene_mode != "simple": if understanding.scene_mode != "simple":
scopes.append("pattern") scopes.append("pattern")
@@ -101,6 +113,8 @@ class PipelineStages:
relative_refs = _extract_relative_refs(user_prompt) if understanding.requires_relative_target else [] 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) required_actions = _derive_required_actions(understanding.intent_type, understanding.scene_mode, understanding.risk_flags)
logging.info(f"Context Binding Results: Required Actions={required_actions}, RAG Scopes={scopes}")
return ContextBinding( return ContextBinding(
location_context=retrieved.get("location_context", ""), location_context=retrieved.get("location_context", ""),
pattern_context=retrieved.get("pattern_context", ""), pattern_context=retrieved.get("pattern_context", ""),
@@ -112,10 +126,12 @@ class PipelineStages:
required_actions=required_actions, required_actions=required_actions,
) )
def stage3_bt_planning(self, user_prompt: str, understanding: TaskUnderstanding, context: ContextBinding) -> BTDraft: def stage3_macro_planning(self, user_prompt: str, understanding: TaskUnderstanding, context: ContextBinding) -> BTDraft:
logging.info("========== [Stage 3] Macro Planning (Round 1) ==========")
include_extra_examples = understanding.intent_type == "generic_mission" include_extra_examples = understanding.intent_type == "generic_mission"
package = self.generator.prompt_composer.compose( package = self.generator.prompt_composer.compose_macro(
scene_mode=understanding.scene_mode, scene_mode=understanding.scene_mode,
drone_state=understanding.drone_state,
intent_type=understanding.intent_type, intent_type=understanding.intent_type,
required_actions=context.required_actions, required_actions=context.required_actions,
risk_flags=understanding.risk_flags, risk_flags=understanding.risk_flags,
@@ -127,23 +143,132 @@ class PipelineStages:
include_extra_examples=include_extra_examples, include_extra_examples=include_extra_examples,
) )
final_user_prompt = user_prompt + (package.user_augmentation or "") final_user_prompt = user_prompt + (package.user_augmentation or "")
logging.info(f"[Round 1] System Prompt Preview (first 500 chars):\n{package.system_prompt[:500]}...")
logging.info(f"[Round 1] User Prompt:\n{final_user_prompt}")
payload, reasoning_text, _raw_text = self.generator.llm_gateway.generate_json( payload, reasoning_text, _raw_text = self.generator.llm_gateway.generate_json(
scene_mode=understanding.scene_mode, scene_mode=understanding.scene_mode,
system_prompt=package.system_prompt, system_prompt=package.system_prompt,
user_prompt=final_user_prompt, user_prompt=final_user_prompt,
) )
final_prompt = f"=== System Prompt ===\n{package.system_prompt}\n\n=== User Prompt ===\n{final_user_prompt}" final_prompt = f"=== System Prompt (Macro) ===\n{package.system_prompt}\n\n=== User Prompt ===\n{final_user_prompt}"
macro_tree = payload.get("macro_tree", {})
parameter_requests = payload.get("parameter_requests", [])
logging.info(f"[Round 1] Output Macro Tree:\n{json.dumps(macro_tree, ensure_ascii=False, indent=2)}")
logging.info(f"[Round 1] Output Parameter Requests:\n{json.dumps(parameter_requests, ensure_ascii=False, indent=2)}")
return BTDraft( return BTDraft(
system_prompt=package.system_prompt, system_prompt=package.system_prompt,
user_prompt=final_user_prompt, user_prompt=final_user_prompt,
allowed_nodes=package.allowed_nodes, allowed_nodes=package.allowed_nodes,
llm_raw_json=payload, llm_raw_json=payload,
macro_tree=macro_tree,
parameter_requests=parameter_requests,
reasoning_text=reasoning_text, reasoning_text=reasoning_text,
final_prompt=final_prompt, final_prompt=final_prompt,
) )
def stage4_validate_and_postprocess(self, user_prompt: str, understanding: TaskUnderstanding, context: ContextBinding, draft: BTDraft) -> Dict[str, Any]: def stage4_middleware_resolution(self, draft: BTDraft) -> Dict[str, Any]:
payload = dict(draft.llm_raw_json) """
中间层解析:读取 parameter_requests代理执行查询现有 RAG / 未来 MCP
"""
logging.info("========== [Stage 4] Middleware Resolution ==========")
resolved_data = {}
for req in draft.parameter_requests:
node = req.get("node")
entities = req.get("extracted_entities", {})
if not entities:
continue
# 如果包含 landmark说明是位置查询调用位置RAG
if "landmark" in entities:
query = entities["landmark"]
if "direction" in entities:
query += " " + entities["direction"]
if "distance" in entities:
query += " " + entities["distance"]
# 简单复用现有的地点检索逻辑
retrieved = self.generator.retriever.retrieve(query, scopes=["location"], n_results=1)
waypoint = self.generator.tool_runtime.build_precomputed_waypoint(query, retrieved.get("location_context", ""))
if waypoint:
resolved_data[f"{node}_location"] = waypoint
# 其他实体直接透传作为后续填参参考
resolved_data[f"{node}_entities"] = entities
logging.info(f"中间层已解析实体依赖数据:\n{json.dumps(resolved_data, ensure_ascii=False, indent=2)}")
return resolved_data
def stage5_micro_filling(self, draft: BTDraft, resolved_data: Dict[str, Any], understanding: TaskUnderstanding) -> Dict[str, Any]:
"""
Round 2 微观参数填空加载原子Schema让大模型只做填空题
"""
logging.info("========== [Stage 5] Micro Parameter Filling (Round 2) ==========")
import os
import json
# 从 nodes_schema.json 文件加载原子 Schema
schema_path = os.path.join(self.generator.prompts_dir, "atomic", "nodes_schema.json")
with open(schema_path, "r", encoding="utf-8") as f:
payload = json.load(f)
atomic_schema = {"actions": [], "conditions": []}
# 提取 Macro 树里所有的节点名称
used_nodes = set()
def _extract_nodes(node):
if not isinstance(node, dict): return
if "name" in node:
used_nodes.add(node["name"])
for child in node.get("children", []):
_extract_nodes(child)
if "child" in node:
_extract_nodes(node["child"])
_extract_nodes(draft.macro_tree.get("root", {}))
for item in payload.get("actions", []):
if item.get("name") in used_nodes:
atomic_schema["actions"].append(item)
for item in payload.get("conditions", []):
if item.get("name") in used_nodes:
atomic_schema["conditions"].append(item)
logging.info(f"按需动态注入的原子节点 Schema 列表: {list(used_nodes)}")
# 组装 Prompt
micro_prompt = self.generator.prompt_composer.compose_micro(
macro_tree=draft.macro_tree,
resolved_data=resolved_data,
atomic_schema=atomic_schema
)
logging.info(f"[Round 2] Micro Prompt Preview (first 300 chars):\n{micro_prompt[:300]}...")
# 再次调用模型
final_payload, reasoning, _ = self.generator.llm_gateway.generate_json(
scene_mode=understanding.scene_mode,
system_prompt=micro_prompt,
user_prompt="请直接输出完整的带有 params 参数的 JSON 树结构。",
)
logging.info(f"[Round 2] Final Micro Tree Output:\n{json.dumps(final_payload, ensure_ascii=False, indent=2)}")
# 将原始推理文本记录下来
if draft.reasoning_text:
reasoning_full = f"=== Round 1 Reasoning ===\n{draft.reasoning_text}\n\n=== Round 2 Reasoning ===\n{reasoning}"
draft.reasoning_text = reasoning_full
return final_payload
def stage6_validate_and_postprocess(self, user_prompt: str, understanding: TaskUnderstanding, context: ContextBinding, draft: BTDraft, final_tree: Dict[str, Any]) -> Dict[str, Any]:
logging.info("========== [Stage 6] Validate and Postprocess ==========")
payload = dict(final_tree)
if context.relative_refs and understanding.scene_mode != "simple": if context.relative_refs and understanding.scene_mode != "simple":
payload.setdefault("context", {}) payload.setdefault("context", {})
payload["context"]["relative_refs"] = context.relative_refs payload["context"]["relative_refs"] = context.relative_refs
@@ -160,6 +285,6 @@ class PipelineStages:
if draft.reasoning_text: if draft.reasoning_text:
self.generator.save_reasoning_content(draft.reasoning_text) self.generator.save_reasoning_content(draft.reasoning_text)
self.generator._save_history(user_prompt, payload) self.generator._save_history(user_prompt, payload)
logging.info("✅ 成功生成并验证了PytreePipeline") logging.info("✅ 成功生成并验证了Pytree两阶段Pipeline")
return payload return payload

View File

@@ -4,7 +4,7 @@ import json
import os import os
from dataclasses import dataclass from dataclasses import dataclass
from functools import lru_cache from functools import lru_cache
from typing import Dict, List, Sequence, Set from typing import Any, Dict, List, Sequence, Set
from .manifest_loader import ManifestPromptLoader from .manifest_loader import ManifestPromptLoader
@@ -23,13 +23,14 @@ class PromptComposer:
self._simple_prompt = self.loader.load_text_file("simple_mode_prompt.txt") self._simple_prompt = self.loader.load_text_file("simple_mode_prompt.txt")
@lru_cache(maxsize=64) @lru_cache(maxsize=64)
def _cached_scene_parts(self, scene_mode: str) -> tuple[str, str, str, str]: def _cached_macro_scene_parts(self, drone_state: str) -> tuple[str, str]:
# 固定骨架 header = self._load_partial("macro_header.txt")
header = self._load_partial("header.txt") template = self._load_partial("template_ground.txt" if drone_state == "on_ground" else "template_air.txt")
required_fields = self._load_partial("required_fields.txt") return header, template
standard_template = self._load_partial("standard_template.txt")
examples = self._load_partial("scene1_examples.txt" if scene_mode == "scene1" else "scene4_examples.txt") @lru_cache(maxsize=64)
return header, required_fields, standard_template, examples def _cached_micro_scene_parts(self) -> str:
return self._load_partial("micro_header.txt")
def _load_partial(self, file_name: str) -> str: def _load_partial(self, file_name: str) -> str:
path = os.path.join(self.prompts_dir, "partials", file_name) path = os.path.join(self.prompts_dir, "partials", file_name)
@@ -87,9 +88,10 @@ class PromptComposer:
] ]
) )
def compose( def compose_macro(
self, self,
scene_mode: str, scene_mode: str,
drone_state: str,
intent_type: str, intent_type: str,
required_actions: Sequence[str], required_actions: Sequence[str],
risk_flags: Sequence[str], risk_flags: Sequence[str],
@@ -100,11 +102,11 @@ class PromptComposer:
user_aug = self._build_user_augmentation(context_blocks) user_aug = self._build_user_augmentation(context_blocks)
return PromptPackage(system_prompt=self._simple_prompt, user_augmentation=user_aug, allowed_nodes={}) 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) header, template = self._cached_macro_scene_parts(drone_state)
selected_nodes = self._slice_nodes(required_actions, risk_flags, scene_mode) selected_nodes = self._slice_nodes(required_actions, risk_flags, scene_mode)
node_snippet = self._build_nodes_snippet(selected_nodes) node_snippet = self._build_nodes_snippet(selected_nodes)
parts: List[str] = [header, node_snippet, required_fields, standard_template, examples] parts: List[str] = [header, node_snippet, template]
common_rules = self._load_partial("common_rules.txt") common_rules = self._load_partial("common_rules.txt")
if common_rules: if common_rules:
parts.append(common_rules) parts.append(common_rules)
@@ -116,6 +118,19 @@ class PromptComposer:
system_prompt = "\n\n".join(p for p in parts if p).strip() 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) return PromptPackage(system_prompt=system_prompt, user_augmentation=self._build_user_augmentation(context_blocks), allowed_nodes=selected_nodes)
def compose_micro(self, macro_tree: Dict[str, Any], resolved_data: Dict[str, Any], atomic_schema: Dict[str, Any]) -> str:
header = self._cached_micro_scene_parts()
parts = [
header,
"## 1. 原宏观骨架树 (macro_tree)",
"```json\n" + json.dumps(macro_tree, ensure_ascii=False, indent=2) + "\n```",
"## 2. 确切数据字典 (resolved_data)",
"```json\n" + json.dumps(resolved_data, ensure_ascii=False, indent=2) + "\n```",
"## 3. 原子节点规范 (atomic_schema)",
"```json\n" + json.dumps(atomic_schema, ensure_ascii=False, indent=2) + "\n```",
]
return "\n\n".join(parts)
def _build_user_augmentation(self, context_blocks: Dict[str, str]) -> str: def _build_user_augmentation(self, context_blocks: Dict[str, str]) -> str:
ordered = [("地点知识", "location"), ("任务模式", "pattern"), ("规则知识", "rules")] ordered = [("地点知识", "location"), ("任务模式", "pattern"), ("规则知识", "rules")]
chunks: List[str] = [] chunks: List[str] = []

View File

@@ -0,0 +1,157 @@
{
"actions": [
{
"name": "takeoff",
"desc": "起飞并达到指定高度",
"params": {
"altitude": "float,默认2.0"
}
},
{
"name": "land",
"desc": "降落",
"params": {
"mode": "'current'/'home'"
}
},
{
"name": "fly_to_waypoint",
"desc": "飞往指定坐标",
"params": {
"x": "float,±10000",
"y": "float,±10000",
"z": "float,[1,5000]",
"acceptance_radius": "float,默认2.0"
}
},
{
"name": "fly_sequence",
"desc": "按顺序飞往多个航点",
"params": {
"waypoints": "list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...]depth可选)",
"coordinate_frame": "'global'/'local_enu'",
"speed": "float,可选"
}
},
{
"name": "move_direction",
"desc": "向指定方向移动",
"params": {
"direction": "north/south/east/west/forward/backward/left/right/up/down",
"distance": "float,默认0",
"speed": "float,可选"
}
},
{
"name": "approach_target",
"desc": "靠近目标",
"params": {
"target_class": "string,要趋近的目标类别",
"description": "string,可选,目标属性描述",
"stop_distance": "float,默认2.0",
"speed": "float,可选,期望的逼近速度"
}
},
{
"name": "rotate",
"desc": "旋转",
"params": {
"angle": "float,无人机自身旋转角度(正数逆时针,负数顺时针)",
"angular_velocity": "float,默认1.0"
}
},
{
"name": "rotate_search",
"desc": "旋转并搜索目标",
"params": {
"target_class": "要搜索的目标类别",
"description": "string,可选,目标属性描述",
"step_angle": "float,可选,每一步旋转的角度",
"total_rotation": "float,可选,总共旋转搜索的角度"
}
},
{
"name": "manual_confirmation",
"desc": "等待人工确认",
"params": {}
},
{
"name": "loiter",
"desc": "悬停",
"params": {
"duration": "int,秒,默认0"
}
},
{
"name": "object_detect",
"desc": "检测目标",
"params": {
"target_class": "检测的目标类别",
"description": "可选",
"count": 1
}
},
{
"name": "search_pattern",
"desc": "按模式搜索",
"params": {
"pattern_type": "spiral/grid",
"center_x": "float,±10000",
"center_y": "float,±10000",
"center_z": "float,[1,5000]",
"radius": "float,[5,1000]",
"target_class": "目标类别",
"description": "可选",
"count": 1
}
},
{
"name": "track_object",
"desc": "跟踪目标",
"params": {
"target_class": "目标类别",
"description": "可选",
"track_time": "int,秒,默认10",
"min_confidence": "float,默认0.7",
"safe_distance": "int,默认10"
}
},
{
"name": "deliver_payload",
"desc": "投放物资",
"params": {
"payload_type": "string",
"release_altitude": "[2,100]默认5"
}
},
{
"name": "return_emergency",
"desc": "紧急返航",
"params": {
"reason": "string"
}
},
{
"name": "take_photos",
"desc": "拍照",
"params": {
"target_class": "目标类别",
"description": "可选",
"track_time": "int,秒,默认10",
"min_confidence": "float,默认0.7",
"safe_distance": "int,默认10"
}
}
],
"conditions": [
{
"name": "object_detected",
"desc": "是否检测到目标",
"params": {
"target_class": "目标类别(必传)",
"description": "可选",
"count": 1
}
}
]
}

View File

@@ -1,46 +1,55 @@
## 六、高频错误规避
1. 控制流节点的 `type` 必须是 `"Sequence"`, `"Selector"` 或 `"Parallel"`
2. **人工确认节点 (`manual_confirmation`) 使用原则**
- **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。
- **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。
3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。
4. 当使用rotate_search或者object_detect节点时必须有object_detected节点
5. 用户指令中要求在当前位置执行任务时无需fly_to_waypoint节点
6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时严禁使用system_checks与takeoff节点仅当用户指令明确在**地面**时,才可使用这两个节点
7. **重点关注**fly_to_waypoint与return_emergency节点辨析当指令包含具体目的地如“紧急回到广场”、“飞回大门”**必须**使用fly_to_waypoint节点**绝对禁止**使用return_emergency节点该节点仅用于无目的地的“返航”指令
8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。
9. **方向移动优先原则**当指令为“快速去往东边100米”直接使用 `move_direction` 节点+距离参数严禁使用fly_to_waypoint
10. **无地点名词禁止飞点**:当指令仅包含“方向 + 距离”且**没有具体地点名词**时,**无论无人机在地面或空中**,都必须使用 `move_direction`,严禁使用 `fly_to_waypoint` 或任何地点坐标。
示例(必须遵守) ## 六、高频错误规避(硬规则,必须遵守)
- “无人机当前在地面快速去往东边60米有身穿白色衣服头戴帽子的男子在挟持他人对其进行拍照。” → 必须使用 `move_direction`east, 60不得生成 `fly_to_waypoint` 或引用任何地点坐标。
- “无人机当前在空中快速去往东边60米有身穿白色衣服头戴帽子的男子在挟持他人对其进行拍照。” → 同样必须使用 `move_direction`east, 60不得生成 `fly_to_waypoint` 或引用任何地点坐标。 0. 严格 JSON 输出:
- 只能输出一个 JSON 对象。
- 禁止输出 Markdown、解释、注释、代码块标记。
- 禁止在 JSON 中出现 // 或 /* */ 注释,禁止尾随逗号。
1. 控制流节点的 type 只能是 "Sequence" / "Selector" / "Parallel"。
2. 人工确认节点manual_confirmation
- 必须添加:仅当指令明确包含“我确认 / 等待确认 / 经允许 / 我通过后 / 允许后”等人工介入关键词时,必须在相应动作前添加 manual_confirmation。
- 严禁添加:指令未提及人工确认时,严禁主动添加 manual_confirmation。
3. 搜索-条件-拍照链路:
- 若出现 condition: object_detected则在其前必须有搜索类 action优先 rotate_search仅当需大范围移动时用 search_pattern
- 若使用 rotate_search 或 object_detect则后续必须出现 object_detected同 target_class
4. 当前位置任务:
- 指令明确“在当前位置 / 原地 / 不用过去 / 就在这”时,无需 fly_to_waypoint。
5. 严格区分无人机状态:
- 指令明确“当前在空中”:严禁使用 system_checks 与 takeoff。
- 指令明确“当前在地面”:如需要飞行任务,优先 system_checks -> takeoff 再执行后续动作。
6. fly_to_waypoint 与 return_emergency 辨析:
- 若指令包含具体目的地(例如“回到广场 / 飞回大门 / 去机库”(即使包含“紧急”二字)),必须使用 fly_to_waypoint绝对禁止使用 return_emergency。
- return_emergency 仅用于“无明确目的地”的立即返航(默认回起飞点)。
7. approach_target 使用:
- 指令提及“靠近 / 飞近 / 贴近 / 距离XX米拍清楚”时必须在 take_photos 之前使用 approach_target。
- 未提及上述关键词时,严禁添加 approach_target。
8. 方向移动优先:
- 当指令仅为“方向 + 距离”且没有具体地点名词时(例如“往东 60 米”),必须使用 move_direction严禁使用 fly_to_waypoint 或任何地点坐标。
9. loiter 参数名:
- loiter 的时间参数一律使用 params.duration单位禁止使用 time。
## 七、坐标计算规则(东南天坐标系 ENU ## 七、坐标计算规则(东南天坐标系 ENU
本系统统一使用东南天ENU坐标系 本系统统一使用东南天ENU坐标系
- **X轴**:正方向为**东** (East),负方向为**西** (West) - X轴正方向 东(East),负方向 西(West)
- **Y轴**:正方向为**南** (South)向为**北** (North) - Y轴正方向 南(South),负方向 北(North)
- **Z轴**:正方向为**天** (Up),负方向为**地** (Down) - Z轴正方向 上(Up),负方向 (Down)
**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才计算绝对坐标并使用`fly_to_waypoint`** 仅当指令涉及“具体地点 + 方向 + 距离”的偏移位置时,才计算绝对坐标并使用 fly_to_waypoint
- 单一方向(东/西/南/北/上/下):必须先调用 calc_offset_enu再使用 fly_to_waypoint。
- 中文复合方位(东南/西北/南偏东10度 等):必须先调用 calc_offset_esu_direction_text再使用 fly_to_waypoint。
- 禁止将复合方位简化为单一方向。
当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具: 当指令只有“方向 + 距离”且没有具体地点名词时,禁止调用 calc_offset_enu必须使用 move_direction。
- **单一方向**(东/西/南/北/上/下如“广场西边200米”**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:
- `base`: 参考地点的ENU坐标含x/y/z
- `direction`: east/west/north/south/up/down
- `distance`: 偏移距离(米)
- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:
- `base`: 参考地点的ENU坐标含x/y/z
- `direction_text`: 中文方位原文如“东南”“南偏东10度”
- `distance`: 偏移距离(米)
- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。
示例(必须遵守):
- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `"东南"``distance` 为 `100`,再使用 `fly_to_waypoint`。
- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `"南偏东10度"``distance` 为 `100`。
当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。
当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。
## 八、输出要求 ## 八、输出要求
仅输出 1 个严格符合上述所有规则的 JSON 对象。 仅输出 1 个严格符合上述所有规则的 JSON 对象。

View File

@@ -2,184 +2,93 @@
"actions": [ "actions": [
{ {
"name": "takeoff", "name": "takeoff",
"params": { "desc": "起飞并达到指定高度"
"altitude": "float[1,100]默认2"
}
}, },
{ {
"name": "land", "name": "land",
"params": { "desc": "降落到地面"
"mode": "'current'/'home'"
}
}, },
{ {
"name": "fly_to_waypoint", "name": "fly_to_waypoint",
"params": { "desc": "飞往指定坐标"
"x": "±10000",
"y": "±10000",
"z": "[1,5000]",
"acceptance_radius": "默认2.0",
"desc": "仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用"
}
}, },
{ {
"name": "fly_sequence", "name": "fly_sequence",
"params": { "desc": "按顺序飞往多个航点"
"waypoints": "list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...]depth可选不填则保持当前高度)",
"coordinate_frame": "'global'/'local_enu'global:经纬度, local_enu:以起飞点为原点的东南天坐标系)",
"speed": "float,可选"
}
}, },
{ {
"name": "move_direction", "name": "move_direction",
"params": { "desc": "向指定方向移动(东西南北等)"
"direction": "north/south/east/west/forward/backward/left/right",
"distance": "[1,10000],缺省则持续移动",
"speed": "float,可选",
"desc": "当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点"
}
}, },
{ {
"name": "approach_target", "name": "approach_target",
"params": { "desc": "靠近已发现的目标"
"target_class": "string,要趋近的目标类别",
"description": "string,可选,目标属性描述",
"stop_distance": "float,期望的最终停止距离",
"speed": "float,可选,期望的逼近速度"
}
}, },
{ {
"name": "rotate", "name": "rotate",
"params": { "desc": "原地旋转自身"
"angle": "float,无人机自身旋转角度(正数逆时针,负数顺时针)",
"angular_velocity": "rad/s,旋转角速度"
}
}, },
{ {
"name": "rotate_search", "name": "rotate_search",
"params": { "desc": "原地旋转并搜索目标"
"target_class": "同object_detect",
"description": "string,可选,目标属性描述",
"step_angle": "float,可选,每一步旋转的角度",
"total_rotation": "float,可选,总共旋转搜索的角度"
}
}, },
{ {
"name": "manual_confirmation", "name": "manual_confirmation",
"params": {} "desc": "等待人工确认"
}, },
{ {
"name": "loiter", "name": "loiter",
"params": { "desc": "原地悬停等待"
"duration": "[1,600]秒/until_condition:可选"
}
}, },
{ {
"name": "object_detect", "name": "object_detect",
"params": { "desc": "检测视野内的目标"
"target_class": "person,bicycle,car,motorcycle,airplane,bus,train,truck,boat,traffic_light,fire_hydrant,stop_sign,parking_meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports_ball,kite,baseball_bat,baseball_glove,skateboard,surfboard,tennis_racket,bottle,wine_glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot_dog,pizza,donut,cake,chair,couch,potted_plant,bed,dining_table,toilet,tv,laptop,mouse,remote,keyboard,cell_phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy_bear,hair_drier,toothbrush,garbage",
"description": "可选,",
"count": "默认1"
}
}, },
{ {
"name": "search_pattern", "name": "search_pattern",
"params": { "desc": "按螺旋或网格模式移动并搜索目标"
"pattern_type": "spiral/grid",
"center_x": "±10000",
"center_y": "±10000",
"center_z": "[1,5000]",
"radius": "[5,1000]",
"target_class": "同object_detect",
"description": "可选,目标属性",
"count": "默认1"
}
}, },
{ {
"name": "track_object", "name": "track_object",
"params": { "desc": "持续跟踪移动的目标"
"target_class": "同object_detect",
"description": "可选,目标属性",
"track_time": "[1,600]秒(必传,不可用'duration'",
"min_confidence": "[0.5,1.0]默认0.7",
"safe_distance": "[2,50]默认10"
}
}, },
{ {
"name": "deliver_payload", "name": "deliver_payload",
"params": { "desc": "投放物资"
"payload_type": "string",
"release_altitude": "[2,100]默认5"
}
}, },
{ {
"name": "system_checks", "name": "return",
"params": { "desc": "返航,回到起飞点"
"check_level": "basic/comprehensive只能在起飞takeoff节点前使用空中无需使用该节点"
}
},
{
"name": "return_emergency",
"params": {
"reason": "string此节点仅用于【无明确目的地】的立即返航默认回起飞点。若指令包含“回到xx地”、“去xx地”即使包含“紧急”二字**严禁**使用此节点必须使用fly_to_waypoint"
}
}, },
{ {
"name": "take_photos", "name": "take_photos",
"params": { "desc": "对目标进行拍照"
"target_class": "同object_detect",
"description": "可选,目标属性",
"track_time": "[1,600]秒(必传,不可用'duration'",
"min_confidence": "[0.5,1.0]默认0.7",
"safe_distance": "[2,50]默认10"
}
} }
], ],
"conditions": [ "conditions": [
{
"name": "at_waypoint",
"params": {
"x": "±10000",
"y": "±10000",
"z": "[1,5000]",
"tolerance": "默认3.0"
}
},
{ {
"name": "object_detected", "name": "object_detected",
"params": { "desc": "判断视野中是否检测到目标"
"target_class": "同object_detect必传",
"description": "可选,目标属性",
"count": "默认1"
}
} }
], ],
"control_flow": [ "control_flow": [
{ {
"name": "Sequence", "name": "Sequence",
"params": {}, "desc": "顺序执行子节点,全成功则成功"
"children": "子节点数组(按序执行,全成功则成功)"
}, },
{ {
"name": "Selector", "name": "Selector",
"params": { "desc": "执行子节点到第一个成功为止"
"memory": "默认true"
},
"children": "子节点数组(执行到成功为止)"
}, },
{ {
"name": "Parallel", "name": "Parallel",
"params": { "desc": "同时执行子节点"
"policy": "all_success/success_on_one"
},
"children": "子节点数组同时执行默认all_success"
} }
], ],
"decorators": [ "decorators": [
{ {
"name": "SuccessIsFailure", "name": "SuccessIsFailure",
"params": {}, "desc": "将子节点的成功反转为失败"
"child": "单一子节点(将子节点的成功结果反转为失败)"
} }
] ]
} }

View File

@@ -1 +0,0 @@
任务根据用户任意任务指令生成结构化可执行的无人机行为树PytreeJSON。**仅输出单一JSON对象无任何自然语言、注释或额外内容**。

View File

@@ -0,0 +1,23 @@
任务:根据用户的自然语言指令,规划无人机的宏观执行流程结构,并提取执行该流程所需的外部参数。
你现在是第一阶段“宏观规划与意图提取”AI。你只需要做两件事
1. 分析意图并排出正确的骨架树(不需要填充任何 parameters/params
2. 从用户指令中提取出需要查询确切位置或目标属性的实体清单(如地标、方向、距离、识别目标)。
**严格约束**:仅输出符合以下 JSON 格式的数据,**禁止**包含任何外部分析、Markdown 标记外的纯文本,或者多余的字段。
输出格式约定:
```json
{
"macro_tree": { ... 纯结构树 ... },
"parameter_requests": [
{
"node": "节点名称",
"intent": "对该节点意图的简短描述",
"extracted_entities": {
"实体key": "实体value"
}
}
]
}
```

View File

@@ -0,0 +1,5 @@
任务根据给定的一棵无参结构树macro_tree、具体的确切数据字典resolved_data以及所需原子节点的参数规范说明atomic_schema补充填满树中各个节点的 `params`。
你现在是第二阶段“微观参数填空”AI。你不需要大改结构你的核心任务是将 `resolved_data` 中的数值或字符串,按照 `atomic_schema` 的要求,填入到对应节点的 `params` 中。
**严格约束**:仅输出单一 JSON 对象(即完整的、包含 `params` 的 PyTree。**禁止**输出任何自然语言分析、前后文或额外注释。

View File

@@ -1,8 +0,0 @@
## 二、节点必填字段后端Schema强制要求缺一验证失败
每个节点必须包含以下字段,字段名/类型不可自定义:
1. **`type`**
- 动作节点→`"action"`,条件节点→`"condition"`,控制流节点→`"Sequence"`/`"Selector"`/`"Parallel"`,装饰器节点→`"decorator"`
2. **`name`**必须是上述JSON中定义的`name`值;
3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;
4. **`children`**:仅控制流节点必含(子节点数组);
5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。

View File

@@ -1,18 +0,0 @@
## 三、标准任务结构模板(单次起降流程)
当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:
```json
{
"root": {
"type": "Sequence",
"name": "MainTask",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}, // 接近目标区域
// --- 核心任务区 (根据指令替换) ---
// 默认不需要降落节点,除非用户明确要求
]
}
}
```
而当无人机在空中时则无需system_checks与takeoff环节直接执行用户任务即可

View File

@@ -0,0 +1,31 @@
## 三、顶层 JSON 结构规范与模板
当无人机状态为 **in_air空中** 时,通常的流程包含:方向移动或直接飞行至目标点 -> (执行任务)。**严禁**使用 `system_checks` 与 `takeoff`。
如果指令要求“沿外围”,需使用 `fly_sequence`。
你必须同时输出 `macro_tree`(必须剔除 params和 `parameter_requests` 两个字段。
结构范例(注意:此仅为结构展示,不代表真实逻辑):
```json
{
"macro_tree": {
"root": {
"type": "Sequence",
"name": "MainTask",
"children": [
{"type": "action", "name": "fly_to_waypoint"},
{"type": "action", "name": "rotate_search"}
]
}
},
"parameter_requests": [
{
"node": "fly_to_waypoint",
"intent": "前往广场中心",
"extracted_entities": {
"landmark": "广场中心"
}
}
]
}
```

View File

@@ -0,0 +1,32 @@
## 三、顶层 JSON 结构规范与模板
当无人机状态为 **on_ground地面** 时,通常的流程包含:`system_checks` -> `takeoff` -> (飞行至目标点等后续动作)。
如果指令要求“沿外围”,需使用 `fly_sequence`。
你必须同时输出 `macro_tree`(必须剔除 params和 `parameter_requests` 两个字段。
结构范例(注意:此仅为结构展示,不代表真实逻辑):
```json
{
"macro_tree": {
"root": {
"type": "Sequence",
"name": "MainTask",
"children": [
{"type": "action", "name": "system_checks"},
{"type": "action", "name": "takeoff"},
{"type": "action", "name": "fly_to_waypoint"}
]
}
},
"parameter_requests": [
{
"node": "fly_to_waypoint",
"intent": "飞行到目标点",
"extracted_entities": {
"landmark": "广场西边"
}
}
]
}
```

View File

@@ -1,24 +1,24 @@
scenes: scenes:
system: system:
- header.txt - macro_header.txt
- core_nodes.json - core_nodes.json
- required_fields.txt - template_ground.txt
- standard_template.txt - template_air.txt
- scene4_examples.txt
- system_extra_examples.txt
- common_rules.txt - common_rules.txt
scene1: scene1:
- header.txt - macro_header.txt
- core_nodes.json - core_nodes.json
- required_fields.txt - template_ground.txt
- standard_template.txt - template_air.txt
- scene1_examples.txt
scene4:
- header.txt
- core_nodes.json
- required_fields.txt
- standard_template.txt
- scene4_examples.txt
- common_rules.txt - common_rules.txt
scene4:
- macro_header.txt
- core_nodes.json
- template_ground.txt
- template_air.txt
- common_rules.txt
simple: simple:
- simple_mode_prompt.txt - simple_mode_prompt.txt

View File

@@ -1,64 +1,9 @@
你是一个严格的指令场景分类器。只输出一个JSON对象,不要输出解释或多余文本 你是指令分类器。只输出一个JSON,无其它内容
根据用户指令与下述场景定义判断其属于“simple / scene1 / scene4”之一 输入:无人机状态{on_ground/in_air}+指令
输出仅三选一:{"mode":"simple"}、{"mode":"scene1"}、{"mode":"scene4"}。
输出格式(严格遵守) 规则
{"mode":"simple"} 或 {"mode":"scene1"} 或 {"mode":"scene4"} 1. 指令含“面前”→scene1
2. 状态=in_air指令是飞到某地/飞到某地+方位距离/往某方向飞X米/降落/旋转/悬停 →simple
判定规则(按顺序执行): 3. 状态=on_ground指令含去/飞到/回到某地 →非simple
1. 先判断是否满足 scene1 或 scene4 的核心特征;若满足,输出对应模式。 4. 多动作/序列任务→scene4
2. 再判断是否满足 simple 的定义(见下);若满足,输出 simple。
3. simple 是“单节点即可完成”的正面定义,不是“既不是 scene1 也不是 scene4”的兜底只有明确符合 simple 定义时才输出 simple。
4. 仅允许以上三种取值,禁止输出其他字段或文本。
—— 场景定义(核心特征 + 任务类型)——
scene1方位态势感知巡查类
- 核心特征:指令依赖“当前可见建筑物”的实时方位/态势——即需要以“面前大楼/这栋楼/当前这栋”等为参照,知道“楼在哪、当前相对楼的位置与高度”,才能规划绕楼、沿外围、在某一高度等。典型表述:面前大楼、这栋楼、绕楼/沿着外围/绕着外围、在楼某高度如12米高处、先上升/下降再绕楼等。
- 任务类型:绕楼外围巡查;在楼外围或指定高度搜索(窗户/杂物/人员等)并拍照;先升降再绕楼侦察。对实时方位、相对建筑物的位置感知要求高。
- 判断要点:若指令中“去某地/飞某地”的“某地”是“面前大楼/这栋楼”或与之强绑定如楼12米高处、楼外围则归 scene1。
scene4命名地点序列复合任务类
- 核心特征指令以“命名地点或区域”为目标如广场、广场南边、施工区域、东边60米等不依赖“面前是哪栋楼”的实时方位感知任务多为“先到某地再在该地做某事”的序列或复合动作搜索、拍照、监控、返航、降落、确认后拍照等
- 任务类型:到某地查找/搜索目标并拍照;到某地后返航/降落;确认后再拍照/返航;持续监控一段时间;到某区域发现某类目标后靠近拍照;紧急回到某地后降落等。对“面前大楼”式的实时方位要求不高。
- 判断要点若指令中的目的地是具名区域或方位广场、广场边上、施工区域、南边40米等且包含“查找/搜索/拍照/返航/监控/确认后”等复合步骤,或需要“先到再做”,则归 scene4。
simple单节点简单指令
- 定义:整条指令有且仅需一个原子动作节点即可完成,无需控制流、无需多步序列。是否“单节点”必须结合无人机当前状态判断。
- 与无人机状态的关系:
- “飞到某地/去某地”:若当前状态为地面,则必须先起飞再飞抵,至少两步,不是 simple应归 scene4或按 scene1 特征判断是否 scene1若当前状态为空中则可直接 fly_to_waypoint为 simple。
- “起飞”“降落”“往某方向飞某距离”(空中时)等单一动作,为 simple。
- 判断要点:先看是否属于 scene1 或 scene4若不属于再看“在当前状态下是否真的只需一个动作”。不能仅凭“不是 scene1、不是 scene4”就判为 simple。
—— 场景指令样例 ——
scene1 示例(围绕“面前大楼/这栋楼”的方位感知与绕楼任务):
- 无人机当前在地面去面前大楼的12米高处绕着外围看有没有打开的窗户发现则进行拍照。
- 无人机当前在地面去面前大楼的12米高处沿着外围查找所有打开的窗户并拍照。
- 无人机当前在空中再往上飞3米接着绕这栋楼外围侦察有没有打开的窗户看到了就拍照传回来。
- 无人机当前在地面去面前大楼的12米高处绕着外围巡视杂物堆积现象发现则进行拍照。
- 无人机当前在地面去面前大楼的12米高处沿着外围查找所有的杂物堆积并拍照。
- 无人机当前在空中往下飞3米接着绕这栋楼外围侦察有没有杂物堆积看到了就拍照传回来。
- 无人机当前在地面去面前大楼的12米高处绕着外围看有没有人发现则进行拍照。
- 无人机当前在地面去面前大楼的12米高处沿着外围逆时针查找所有的人并拍照。
- 无人机当前在空中再往上飞3米接着绕这栋楼外围侦察有没有人看到了就拍照传回来。
scene4 示例(以命名地点/区域为目标的序列或复合任务):
- 无人机当前在地面,到广场查找穿红色衣服的人,找到后近距离拍照。
- 无人机当前在空中,回到广场,对戴帽子的人进行拍照。
- 无人机当前在空中去广场南边40米对过往的公交车拍张照然后返航。
- 无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。
- 无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。
- 无人机当前在空中,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航。
- 无人机当前在空中往广场南边飞40米持续监控5分钟发现人就拍照告诉我到时间可以返航。
- 无人机当前在地面,到广场边上的施工区域内,发现有没带安全帽的飞近后拍照。
- 无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。
- 无人机当前在地面快速去往东边60米有身穿白色衣服头戴帽子的男子在挟持他人对其进行拍照。
- 无人机当前在空中离白色衣服戴帽子的人太远了照片看不清贴近到3米距离拍拍完可以直接返航。
simple 示例(单节点 + 注意无人机状态):
- 无人机当前在地面,起飞。
- 无人机当前在空中,飞到广场。(空中且仅“飞到某地” → 单节点)
- 无人机当前在空中,往北飞 50 米。(空中且仅方向+距离 → 单节点)
- 无人机当前在地面,起飞到 10 米。(仅起飞到高度 → 单节点)
非 simple对比
- 无人机当前在地面,飞到广场。(在地面“飞到某地”需先起飞再飞抵 → 非单节点,归 scene4

View File

@@ -1,54 +1,25 @@
你是一个无人机简单指令执行规划器。你的任务当用户给出“简单指令”单一原子动作即可完成输出一个严格的JSON对象。 你是一个无人机简单指令执行规划器。
假设输入一定是“单一原子动作即可完成”的简单指令。你的任务是输出一个严格的JSON对象。
说明:用户消息末尾可能附带【地点知识】等参考信息(来自 RAG 检索),用于解析"飞到某地"或"某地东边X米"类指令的坐标。请根据参考信息推断 fly_to_waypoint 的 x/y/z若无坐标则用合理估计值。
输出要求(必须遵守): 输出要求(必须遵守):
- 只输出一个JSON对象不要任何解释或多余文本。 - 只输出一个JSON对象不要任何解释或多余文本。
- JSON结构 - JSON结构固定为
{"root":{"type":"action","name":"<action_name>","params":{...}}} {"root":{"type":"action","name":"<action_name>","params":{...}}}
- root节点必须是action类型节点,不能是控制流节点。 - root 节点必须是 action,禁止输出 Sequence/Selector/Parallel 等控制流节点。
- params 只能包含该动作定义内的字段,禁止自定义字段。
- 数值请使用数字类型(例如 10.0)。
可用动作simple 模式只允许从下列动作中选择其一):
1) takeoff: {"altitude": float[1,100]}(仅当地面起飞)
2) land: {"mode": "current"|"home"}
3) fly_to_waypoint: {"x": number, "y": number, "z": number, "acceptance_radius": number(可选)}
4) move_direction: {"direction": "north"|"south"|"east"|"west"|"forward"|"backward"|"left"|"right"|"up"|"down", "distance": number}
5) rotate: {"angle": number, "angular_velocity": number(可选)}
6) loiter: {"duration": number}
示例: 示例:
- “起飞到10米” → {"root":{"type":"action","name":"takeoff","params":{"altitude":10.0}}} - “起飞到10米” → {"root":{"type":"action","name":"takeoff","params":{"altitude":10.0}}}
- “移动到(120,80,20)” → {"root":{"type":"action","name":"fly_to_waypoint","params":{"x":120.0,"y":80.0,"z":20.0,"acceptance_radius":2.0}}} - “往北飞50米” → {"root":{"type":"action","name":"move_direction","params":{"direction":"north","distance":50.0}}}
- “飞机自检” → {"root":{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}}} - “飞到(120,80,20)” → {"root":{"type":"action","name":"fly_to_waypoint","params":{"x":120.0,"y":80.0,"z":20.0,"acceptance_radius":2.0}}}
—— 可用节点定义——
```json
{
"actions": [
{"name":"takeoff","params":{"altitude":"float[1,100]默认2"}},
{"name":"land","params":{"mode":"'current'/'home'"}},
{"name":"fly_to_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","acceptance_radius":"默认2.0"}},
{"name":"fly_sequence","params":{"waypoints":"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...]depth可选不填则保持当前高度)","coordinate_frame":"'global'/'local_enu'global:经纬度, local_enu:以起飞点为原点的东南天坐标系)","speed":"float,可选"}},
{"name":"move_direction","params":{"direction":"north/south/east/west/forward/backward/left/right/up/down","distance":"[1,10000],缺省持续移动","speed":"float,可选"}},
{"name":"approach_target","params":{"target_class":"string,要趋近的目标类别","description":"string,可选,目标属性描述","stop_distance":"float,期望的最终停止距离","speed":"float,可选,期望的逼近速度"}},
{"name":"rotate","params":{"angle":"float,无人机自身旋转角度(正数逆时针,负数顺时针)","angular_velocity":"rad/s,旋转角速度"}},
{"name":"rotate_search","params":{"target_class":"string,要搜寻的目标类别","description":"string,可选,目标属性描述","step_angle":"float,可选,每一步旋转的角度","total_rotation":"float,可选,总共旋转搜索的角度"}},
{"name":"manual_confirmation","params":{}},
{"name":"loiter","params":{"duration":"[1,600]秒/until_condition:可选"}},
{"name":"object_detect","params":{"target_class":"person,bicycle,car,motorcycle,airplane,bus,train,truck,boat,traffic_light,fire_hydrant,stop_sign,parking_meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports_ball,kite,baseball_bat,baseball_glove,skateboard,surfboard,tennis_racket,bottle,wine_glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot_dog,pizza,donut,cake,chair,couch,potted_plant,bed,dining_table,toilet,tv,laptop,mouse,remote,keyboard,cell_phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy_bear,hair_drier,toothbrush,garbage","description":"可选,","count":"默认1"}},
{"name":"strike_target","params":{"target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}},
{"name":"battle_damage_assessment","params":{"target_class":"同object_detect","assessment_time":"[5,60]默认15"}},
{"name":"search_pattern","params":{"pattern_type":"spiral/grid","center_x":"±10000","center_y":"±10000","center_z":"[1,5000]","radius":"[5,1000]","target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}},
{"name":"track_object","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration'","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}},
{"name":"deliver_payload","params":{"payload_type":"string","release_altitude":"[2,100]默认5"}},
{"name":"system_checks","params":{"check_level":"basic/comprehensive"}},
{"name":"return_emergency","params":{"reason":"string此节点仅用于【无明确目的地】的立即返航。若指令包含“回到xx地”、“去xx地”即使包含“紧急”二字**严禁**使用此节点必须使用fly_to_waypoint"}},
{"name":"take_photos","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration'","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}}
],
"conditions": [
{"name":"at_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","tolerance":"默认3.0"}},
{"name":"object_detected","params":{"target_class":"同object_detect必传","description":"可选,目标属性","count":"默认1"}},
{"name":"target_destroyed","params":{"target_class":"同object_detect","description":"可选,目标属性","confidence":"[0.5,1.0]默认0.8"}},
{"name":"time_elapsed","params":{"duration":"[1,2700]秒"}},
{"name":"gps_status","params":{"min_satellites":"int[6,15]必传如8"}}
]
}
```
—— 参数约束——
- takeoff.altitude: [1, 100]
- fly_to_waypoint.z: [1, 5000]
- fly_to_waypoint.x,y: [-10000, 10000]
- search_pattern.radius: [5, 1000]
- move_direction.distance: [1, 10000]
- 若参考知识提供坐标,必须使用并裁剪到约束范围内

View File

@@ -226,12 +226,11 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
"params": { "params": {
"type": "object", "type": "object",
"properties": { "properties": {
"target_class": {"type": "string", "enum": target_classes}, "target_class": {"type": "string"},
"description": {"type": "string"}, "description": {"type": "string"},
"count": {"type": "integer", "minimum": 1} "count": {"type": ["integer", "string"]}
}, },
"required": ["target_class"], "required": ["target_class"]
"additionalProperties": False
} }
} }
} }
@@ -249,12 +248,11 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
"params": { "params": {
"type": "object", "type": "object",
"properties": { "properties": {
"target_class": {"type": "string", "enum": target_classes}, "target_class": {"type": "string"},
"description": {"type": "string"}, "description": {"type": "string"},
"count": {"type": "integer", "minimum": 1} "count": {"type": ["integer", "string"]}
}, },
"required": ["target_class"], "required": ["target_class"]
"additionalProperties": False
} }
} }
} }
@@ -275,7 +273,7 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
"threshold": {"type": "number", "minimum": 0.0, "maximum": 1.0} "threshold": {"type": "number", "minimum": 0.0, "maximum": 1.0}
}, },
"required": ["threshold"], "required": ["threshold"],
"additionalProperties": False "additionalProperties": True
} }
} }
} }
@@ -296,7 +294,7 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
"min_satellites": {"type": "integer", "minimum": 6, "maximum": 15} "min_satellites": {"type": "integer", "minimum": 6, "maximum": 15}
}, },
"required": ["min_satellites"], "required": ["min_satellites"],
"additionalProperties": False "additionalProperties": True
} }
} }
} }
@@ -331,7 +329,7 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
"speed": {"type": "number"} "speed": {"type": "number"}
}, },
"required": ["waypoints", "coordinate_frame"], "required": ["waypoints", "coordinate_frame"],
"additionalProperties": False "additionalProperties": True
} }
} }
} }
@@ -355,7 +353,7 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
"speed": {"type": "number"} "speed": {"type": "number"}
}, },
"required": ["target_class", "stop_distance"], "required": ["target_class", "stop_distance"],
"additionalProperties": False "additionalProperties": True
} }
} }
} }
@@ -404,7 +402,7 @@ def _generate_simple_mode_schema(allowed_actions: set) -> dict:
} }
}, },
"required": ["root"], # 顶层必须有root字段 "required": ["root"], # 顶层必须有root字段
"additionalProperties": False # 顶层只能有root字段不能有其他字段如mode等 "additionalProperties": True # 顶层只能有root字段不能有其他字段如mode等
} }
return schema return schema
@@ -720,6 +718,10 @@ class PyTreeGenerator:
return self._load_prompt(fallback_file) return self._load_prompt(fallback_file)
scene_map = manifest.get("scenes", manifest) scene_map = manifest.get("scenes", manifest)
if scene_map is None:
scene_map = manifest
logging.error(f"DEBUG: manifest={manifest} scene_map={scene_map} type={type(scene_map)}")
fragments = scene_map.get(scene_key) fragments = scene_map.get(scene_key)
if not fragments: if not fragments:
logging.warning(f"提示词清单缺少场景配置 -> {scene_key}") logging.warning(f"提示词清单缺少场景配置 -> {scene_key}")

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