21 Commits

Author SHA1 Message Date
lulijing
e164148c5c 提示词分段编排解耦 2026-01-29 15:21:32 +08:00
lulijing
b12339ff73 新增方位解析function call 2026-01-29 14:27:56 +08:00
410d2e01e4 修改启动脚本 2026-01-29 10:27:36 +08:00
070e4f579d 提示词流程分类 2026-01-29 10:24:02 +08:00
dd066057b0 增加场景4示例 2026-01-27 16:38:06 +08:00
59c52f6b99 提示词修改 2026-01-22 17:38:24 +08:00
bce9203e01 进一步优化指令适配,指定温度采样参数 2026-01-21 13:23:49 +08:00
333fad40ac 修改为东南天坐标系 2026-01-20 09:49:52 +08:00
9538757047 修改坐标系 2026-01-19 15:20:15 +08:00
ffb9aee730 Add coordinate tool flow and tighten tool usage
Introduce ENU offset tool support with controlled tool-call handling, update prompts to gate coordinate tooling, and enable jinja tools in startup.
2026-01-19 15:01:04 +08:00
b68883e4e0 完善修改 2026-01-19 13:41:59 +08:00
5d1c02fb5b 针对场景4进行修改 2026-01-16 16:05:52 +08:00
fb473dcf1a 删除自动降落 2026-01-08 16:09:48 +08:00
10c5bb5a8a 增加环绕侦察场景适配 2026-01-08 15:44:38 +08:00
3eba1f962b 解决冲突:以本地版本为准更新 system_prompt.txt 和 py_tree_generator.py 2026-01-07 16:38:40 +08:00
6f990e645d 优化交互式测试验证脚本,针对场景4修改提示词以及代码 2026-01-02 16:28:58 +08:00
c08cdfb339 修改简单模式验证 2025-12-03 17:13:59 +08:00
43a0636913 修改简单模式验证 2025-12-03 17:13:47 +08:00
c4f851d387 chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境
- 包含所有Python依赖包
- 注意:虚拟环境约393MB,包含12655个文件
2025-12-03 10:19:25 +08:00
a6c2027caa feat: 添加一键启动脚本并更新项目配置
- 添加 start_all.sh 一键启动脚本,支持启动llama-server和FastAPI服务
- 修改启动脚本使用venv虚拟环境替代conda环境
- 更新README.md,添加一键启动脚本使用说明
- 更新py_tree_generator.py,添加final_prompt返回字段
- 禁用Qwen3模型的思考功能
- 添加RAG检索结果的终端打印
- 移除ROS2相关代码(ros2_client.py已删除)
2025-12-02 21:42:26 +08:00
ab6e09423b 去除ROS2相关内容,新增一键启动脚本 2025-12-02 21:41:18 +08:00
15901 changed files with 3707651 additions and 69879 deletions

View File

@@ -228,14 +228,96 @@ python ingest.py
完成前两个阶段后,即可启动并测试后端服务。 完成前两个阶段后,即可启动并测试后端服务。
#### 1. 启动后端服务 #### 1. 启动所有服务(推荐方式:一键启动脚本)
启动服务的关键在于**按顺序激活环境**先激活ROS 2工作空间再激活Conda环境。 我们提供了一个一键启动脚本 `start_all.sh`,可以自动启动所有必需的服务:
```bash ```bash
# 1. 切换到项目根目录 # 1. 切换到项目根目录
cd /path/to/your/drone 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
# 查看所有日志
tail -f logs/*.log
# 查看特定服务日志
tail -f logs/inference_model.log # 推理模型
tail -f logs/embedding_model.log # Embedding模型
tail -f logs/fastapi.log # FastAPI服务
```
#### 2. 手动启动服务(备选方式)
如果您需要手动控制每个服务的启动,可以按照以下步骤操作:
**启动推理模型服务:**
```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编译环境 # 2. 激活ROS 2编译环境
# 作用:将我们编译好的`drone_interfaces`包的路径告知系统否则Python会报`ModuleNotFoundError`。 # 作用:将我们编译好的`drone_interfaces`包的路径告知系统否则Python会报`ModuleNotFoundError`。
# 注意:此命令必须在每次打开新终端时执行一次。 # 注意:此命令必须在每次打开新终端时执行一次。
@@ -248,6 +330,7 @@ conda activate backend
cd backend_service/ cd backend_service/
uvicorn src.main:app --host 0.0.0.0 --port 8000 uvicorn src.main:app --host 0.0.0.0 --port 8000
``` ```
当您看到日志中出现 `Uvicorn running on http://0.0.0.0:8000` 时,表示服务已成功启动。 当您看到日志中出现 `Uvicorn running on http://0.0.0.0:8000` 时,表示服务已成功启动。
#### 2. 运行API接口测试 #### 2. 运行API接口测试

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -15,8 +15,8 @@ chromadb>=0.4.0
# Visualization # Visualization
graphviz>=0.20.0 graphviz>=0.20.0
# ROS 2 Python Client # ROS 2 Python Client - 已注释项目已与ROS2解耦
rclpy>=0.0.1 # rclpy>=0.0.1
# Document Processing # Document Processing
unstructured[all]>=0.11.0 unstructured[all]>=0.11.0
@@ -30,10 +30,10 @@ rich>=13.7.0
# Type Hints Support # Type Hints Support
typing-extensions>=4.8.0 typing-extensions>=4.8.0
# ROS 2 Build Dependencies # ROS 2 Build Dependencies - 已注释项目已与ROS2解耦
empy==3.3.4 # empy==3.3.4
catkin-pkg>=0.4.0 # catkin-pkg>=0.4.0
lark>=1.1.0 # lark>=1.1.0
colcon-common-extensions>=0.3.0 # colcon-common-extensions>=0.3.0
vcstool>=0.2.0 # vcstool>=0.2.0
rosdep>=0.22.0 # rosdep>=0.22.0

Binary file not shown.

View File

@@ -3,13 +3,13 @@ import os
from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
import logging import logging
import threading # import threading # ROS2相关已注释
import rclpy # import rclpy # ROS2相关已注释
from .models import GeneratePlanRequest, ExecuteMissionRequest from .models import GeneratePlanRequest, ExecuteMissionRequest
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 # from .ros2_client import MissionActionClient # ROS2相关已注释
# --- Application Setup --- # --- Application Setup ---
app = FastAPI( app = FastAPI(
@@ -23,14 +23,15 @@ static_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'gene
app.mount("/static", StaticFiles(directory=static_dir), name="static") app.mount("/static", StaticFiles(directory=static_dir), name="static")
# --- ROS2 Node and Client Initialization --- # --- ROS2 Node and Client Initialization ---
rclpy.init() # ROS2相关代码已注释项目已与ROS2解耦
ros2_client = MissionActionClient() # rclpy.init()
# ros2_client = MissionActionClient()
def run_ros2_node(): # def run_ros2_node():
"""Spins the ROS2 node in a dedicated thread.""" # """Spins the ROS2 node in a dedicated thread."""
logging.info("Starting to spin ROS2 node...") # logging.info("Starting to spin ROS2 node...")
rclpy.spin(ros2_client) # rclpy.spin(ros2_client)
logging.info("ROS2 node has stopped spinning.") # logging.info("ROS2 node has stopped spinning.")
# --- API Endpoints --- # --- API Endpoints ---
@@ -49,9 +50,12 @@ async def generate_plan_endpoint(request: GeneratePlanRequest):
async def execute_mission_endpoint(request: ExecuteMissionRequest): async def execute_mission_endpoint(request: ExecuteMissionRequest):
""" """
Receives a `py_tree.json` and sends it to the drone for execution. Receives a `py_tree.json` and sends it to the drone for execution.
ROS2相关功能已注释项目已与ROS2解耦。
""" """
ros2_client.send_goal(request.py_tree) # ROS2相关代码已注释
return {"status": "execution_started"} # ros2_client.send_goal(request.py_tree)
logging.warning("execute_mission endpoint called but ROS2 is disabled. Mission execution is not available.")
return {"status": "execution_disabled", "message": "ROS2 integration is disabled. Mission execution is not available."}
@app.websocket("/ws/status") @app.websocket("/ws/status")
async def websocket_endpoint(websocket: WebSocket): async def websocket_endpoint(websocket: WebSocket):
@@ -73,21 +77,23 @@ async def websocket_endpoint(websocket: WebSocket):
async def startup_event(): async def startup_event():
""" """
On startup, get the current asyncio event loop and pass it to the websocket manager. On startup, get the current asyncio event loop and pass it to the websocket manager.
Also, start the ROS2 node in a background thread. ROS2相关功能已注释项目已与ROS2解耦。
""" """
# Configure WebSocket Manager # Configure WebSocket Manager
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
websocket_manager.set_loop(loop) websocket_manager.set_loop(loop)
logging.info("WebSocket event loop configured.") logging.info("WebSocket event loop configured.")
# ROS2相关代码已注释
# Start ROS2 node in a background thread # Start ROS2 node in a background thread
ros2_thread = threading.Thread(target=run_ros2_node, daemon=True) # ros2_thread = threading.Thread(target=run_ros2_node, daemon=True)
ros2_thread.start() # ros2_thread.start()
logging.info("ROS2 node thread started.") # logging.info("ROS2 node thread started.")
@app.on_event("shutdown") @app.on_event("shutdown")
async def shutdown_event(): async def shutdown_event():
logging.info("Backend service shutting down.") logging.info("Backend service shutting down.")
ros2_client.destroy_node() # ROS2相关代码已注释
rclpy.shutdown() # ros2_client.destroy_node()
logging.info("ROS2 node shut down successfully.") # rclpy.shutdown()
# logging.info("ROS2 node shut down successfully.")

View File

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

View File

@@ -0,0 +1,41 @@
## 六、高频错误规避
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
## 七、坐标计算规则(东南天坐标系 ENU
本系统统一使用东南天ENU坐标系
- **X轴**:正方向为**东** (East),负方向为**西** (West)
- **Y轴**:正方向为**南** (South)向为**北** (North)
- **Z轴**:正方向为**天** (Up),负方向为**地** (Down)
**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`**
当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具:
- **单一方向**(东/西/南/北/上/下如“广场西边200米”**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数:
- `base`: 参考地点的ENU坐标含x/y/z
- `direction`: east/west/north/south/up/down
- `distance`: 偏移距离(米)
- **中文复合方位**(东南/西北/东北/西南/南偏东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对象。

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,448 @@
## 四、场景示例
#### 场景 1地面到12米绕外围查看打开的窗户Sequence
**指令**“无人机当前在地面去面前大楼的12米高处绕着外围看有没有打开的窗户发现则进行拍照。”
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoWindows",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectWindows",
"params": {"policy": "success_on_one"},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectOpenWindowsWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"打开的窗户","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 2地面到12米沿外围查找打开的窗户Sequence
**指令**“无人机当前在地面去面前大楼的12米高处沿着外围查找所有打开的窗户并拍照。”
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoOpenWindows",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectOpenWindows",
"params": {"policy": "success_on_one"},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectAllOpenWindowsWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"打开的窗户","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 3空中上升后绕外围侦察打开窗户Sequence
**指令**“无人机当前在空中再往上飞3米接着绕这栋楼外围侦察有没有打开的窗户看到了就拍照传回来。”
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "RiseAndDetectOpenWindows",
"children": [
{"type":"action","name":"move_direction","params":{"direction":"up","distance":3.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectOpenWindows",
"params": {"policy": "success_on_one"},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8},
{"x": -108.5, "y": 241.8},
{"x": -108.5, "y": 289.8},
{"x": -24.0, "y": 292.8},
{"x": -24.0, "y": 241.8}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectOpenWindowsWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"打开的窗户","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 4地面到12米绕外围巡视杂物堆积Sequence
**指令**“无人机当前在地面去面前大楼的12米高处绕着外围巡视杂物堆积现象发现则进行拍照。”
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoGarbageObserve",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectGarbageObserve",
"params": {"policy": "success_on_one"},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectGarbageWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"condition","name":"object_detected","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"action","name":"take_photos","params":{"target_class":"garbage","description":"杂物堆积","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 5地面到12米沿外围查找杂物堆积Sequence
**指令**“无人机当前在地面去面前大楼的12米高处沿着外围查找所有的杂物堆积并拍照。”
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoGarbage",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectGarbage",
"params": {"policy": "success_on_one"},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectGarbageWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"condition","name":"object_detected","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"action","name":"take_photos","params":{"target_class":"garbage","description":"杂物堆积","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 6空中下降后绕外围侦察杂物堆积Sequence
**指令**“无人机当前在空中往下飞3米接着绕这栋楼外围侦察有没有杂物堆积看到了就拍照传回来。”
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "DescendAndDetectGarbage",
"children": [
{"type":"action","name":"move_direction","params":{"direction":"down","distance":3.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectGarbage",
"params": {"policy": "success_on_one"},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8},
{"x": -108.5, "y": 241.8},
{"x": -108.5, "y": 289.8},
{"x": -24.0, "y": 292.8},
{"x": -24.0, "y": 241.8}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectGarbageWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"condition","name":"object_detected","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"action","name":"take_photos","params":{"target_class":"garbage","description":"杂物堆积","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 7地面到12米绕外围查看是否有人Sequence
**指令**“无人机当前在地面去面前大楼的12米高处绕着外围看有没有人发现则进行拍照。”
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoPerson",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectPerson",
"params": {"policy": "success_on_one"},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectPersonWhileFlying",
"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}}
]
}
}
]
}
]
}
}
```
#### 场景 8地面到12米沿外围逆时针查找人员Sequence
**指令**“无人机当前在地面去面前大楼的12米高处沿着外围逆时针查找所有的人并拍照。”
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterCounterClockwiseAndPhotoPerson",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolCCWAndDetectPerson",
"params": {"policy": "success_on_one"},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectPersonWhileFlying",
"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}}
]
}
}
]
}
]
}
}
```
#### 场景 9空中上升后沿建筑外围侦察人员Sequence
**指令**“无人机当前在空中再往上飞3米接着绕这栋楼外围侦察有没有人看到了就拍照传回来。”
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "RiseAndPatrolPerimeter",
"children": [
{"type":"action","name":"move_direction","params":{"direction":"up","distance":3.0}},
{
"type": "Parallel",
"name": "PatrolAndDetect",
"params": {"policy": "success_on_one"},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8},
{"x": -108.5, "y": 241.8},
{"x": -108.5, "y": 289.8},
{"x": -24.0, "y": 292.8},
{"x": -24.0, "y": 241.8}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectAndPhotoWhileFlying",
"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}}
]
}
}
]
}
]
}
}
```

View File

@@ -0,0 +1,208 @@
## 四、场景示例(请灵活参考)
#### 场景 1线性搜索任务Sequence + Selector
**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。”
**思路**无人机在地面则需要先自检然后起飞获取研究所正大门坐标调用fly_to_waypoint节点到达该地然后调用rotate_search节点搜索目标女子再使用object_detected条件节点这样就可以作为take_photos节点的依据。
**结构**Sequence (按顺序执行)
```json
{
"root": {
"type": "Sequence",
"name": "MainSearchTask",
"children": [
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}},
{"type":"action","name":"rotate_search","params":{"target_class":"person","description":"扎辫子女子"}},
{
"type": "Selector",
"name": "CheckAndPhoto",
"children": [
{
"type": "Sequence",
"name": "PhotoIfFound",
"children": [
{"type":"condition","name":"object_detected","params":{"target_class":"person","description":"扎辫子女子"}},
{"type":"action","name":"take_photos","params":{"target_class":"person","description":"扎辫子女子","track_time":10.0}}
]
},
{"type":"action","name":"loiter","params":{"duration":5.0}} // 未发现时的备选动作
]
}
]
}
}
```
#### 场景 2带中断逻辑的巡逻Selector 示例)
**指令**“无人机当前在地面飞往航点A。如果途中发现可疑人员则悬停。”
**参考知识**航点A的坐标x:100.0,y:50.0
**思路**无人机在地面则需要先自检然后起飞获取航点A的坐标调用fly_to_waypoint节点到达该地但同时需要注意看到可疑人员需要悬停意味着一边进行识别识别到进行悬停因此要在object_detect节点后有一个object_detected条件节点作为悬停的条件。
**结构**
```json
{
"root": {
"type": "Sequence",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"basic"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Selector",
"name": "FlyOrDetect",
"children": [
{
"type": "Sequence",
"name": "InterruptionLogic",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"person"}},
{"type":"condition","name":"object_detected","params":{"target_class":"person"}},
{"type":"action","name":"loiter","params":{"duration":5.0}}
]
},
{"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}
]
}
]
}
}
```
#### 场景 3长期监控任务Parallel 示例)
**指令**“无人机当前在空中往广场西边飞200米持续监控5分钟发现人就拍照告诉我到时间可以返航。”
**参考知识**广场西边200米的坐标x:50.0,y:50.0,z:10.0
**思路**无人机在空中直接前往目标点到达后需要并行执行两件事1. 倒计时5分钟主控时间2. 持续检测人并拍照(从属任务)。
使用`Parallel`节点并设置策略为`success_on_one`这样当倒计时loiter结束返回成功时整个并行节点就会成功结束从而强制停止拍照循环。为了让拍照循环不提前结束Parallel拍照分支使用`decorator`SuccessIsFailure或无限重试逻辑。
**结构**
```json
{
"root": {
"type": "Sequence",
"name": "MainTask",
"children": [
{
"type": "action",
"name": "fly_to_waypoint",
"params": {
"x": 50.0,
"y": 50.0,
"z": 10.0
}
},
{
"type": "Parallel",
"name": "MonitorAndPhoto",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "loiter",
"params": {
"time": 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": "任务完成"
}
}
]
}
}
```
#### 场景 4交互式确认任务Sequence + Manual Confirmation
**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。”
**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过返回Success才会继续执行后续的`take_photos`动作。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "SearchConfirmPhoto",
"children": [
{"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}},
{"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}},
{"type":"action","name":"manual_confirmation","params":{}},
{"type":"action","name":"take_photos","params":{"target_class":"car","description":"小汽车"}}
]
}
}
```
#### 场景 5后置确认任务Sequence + Manual Confirmation
**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航”
**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "SearchPhotoConfirmReturn",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}},
{"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}},
{"type":"action","name":"take_photos","params":{"target_class":"car","description":"小汽车"}},
{"type":"action","name":"manual_confirmation","params":{}},
{"type":"action","name":"return_emergency","params":{"reason":"确认后返航"}}
]
}
}
```
#### 场景 6移动后条件降落Sequence
**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。”
**参考知识**广场坐标x:0.0, y:0.0, z:10.0
**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "FlySearchLand",
"children": [
{"type":"action","name":"fly_to_waypoint","params":{"x":0.0,"y":0.0,"z":10.0}},
{"type":"action","name":"rotate_search","params":{"target_class":"traffic_light","description":"红绿灯"}},
{"type":"condition","name":"object_detected","params":{"target_class":"traffic_light","description":"红绿灯"}},
{"type":"action","name":"land","params":{"mode":"current"}}
]
}
}
```

View File

@@ -0,0 +1,18 @@
## 三、标准任务结构模板(单次起降流程)
当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:
```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,489 @@
#### 场景 7空中上升后沿建筑外围侦察Sequence + Selector
**指令**“无人机当前在空中再往上飞3米接着绕这栋楼外围侦察有没有人看到了就拍照传回来。”
**参考信息**面前的大楼外围四个点东南天坐标系坐标A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。
**思路**无人机在空中无需自检与起飞。先向上移动3米仅方向+距离,使用`move_direction`)。随后“边飞边判断”:用`Parallel`并行执行`fly_sequence`与持续检测分支。检测分支使用`object_detect` + `object_detected` + `take_photos`,再用`SuccessIsFailure`保证不会提前结束并行;并行策略为`success_on_one`,当巡查飞行完成即结束任务。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "RiseAndPatrolPerimeter",
"children": [
{
"type": "action",
"name": "move_direction",
"params": {
"direction": "up",
"distance": 3.0
}
},
{
"type": "Parallel",
"name": "PatrolAndDetect",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8},
{"x": -108.5, "y": 241.8},
{"x": -108.5, "y": 289.8},
{"x": -24.0, "y": 292.8},
{"x": -24.0, "y": 241.8}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectAndPhotoWhileFlying",
"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}}
]
}
}
]
}
]
}
}
```
#### 场景 8地面起飞到楼前12米高度并沿外围查找杂物Sequence
**指令**“无人机当前在地面去面前大楼的12米高处沿着外围查找所有的杂物堆积并拍照。”
**参考信息**面前的大楼外围四个点东南天坐标系坐标A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。
**思路**:无人机在地面,需自检后起飞。指令是到具体地点且明确高度,使用`fly_sequence`沿外围巡查所有航点设置为12米高度`depth`=12。为满足“边飞边判断”使用`Parallel`并行飞行与检测,检测分支持续执行`object_detect` + `object_detected` + `take_photos`,用`SuccessIsFailure`避免提前结束。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoGarbage",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectGarbage",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectGarbageWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"condition","name":"object_detected","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"action","name":"take_photos","params":{"target_class":"garbage","description":"杂物堆积","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 9地面到12米绕外围查看打开的窗户Sequence
**指令**“无人机当前在地面去面前大楼的12米高处绕着外围看有没有打开的窗户发现则进行拍照。”
**参考信息**面前的大楼外围四个点东南天坐标系坐标A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。
**思路**:无人机在地面,先自检起飞。随后使用`fly_sequence`沿外围绕飞航点高度为12米`depth`=12。同时并行执行检测分支发现打开窗户就拍照。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoWindows",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectWindows",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectOpenWindowsWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"打开的窗户","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 10地面到12米沿外围查找打开的窗户Sequence
**指令**“无人机当前在地面去面前大楼的12米高处沿着外围查找所有打开的窗户并拍照。”
**参考信息**面前的大楼外围四个点东南天坐标系坐标A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。
**思路**:无人机在地面,起飞后使用`fly_sequence`沿外围巡查(`depth`=12并行持续检测打开的窗户并拍照。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoOpenWindows",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectOpenWindows",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectAllOpenWindowsWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"打开的窗户","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 11空中上升后绕外围侦察打开窗户Sequence
**指令**“无人机当前在空中再往上飞3米接着绕这栋楼外围侦察有没有打开的窗户看到了就拍照传回来。”
**参考信息**面前的大楼外围四个点东南天坐标系坐标A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。
**思路**无人机在空中先上升3米。随后并行执行绕飞巡查与检测发现打开窗户立即拍照。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "RiseAndDetectOpenWindows",
"children": [
{"type":"action","name":"move_direction","params":{"direction":"up","distance":3.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectOpenWindows",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8},
{"x": -108.5, "y": 241.8},
{"x": -108.5, "y": 289.8},
{"x": -24.0, "y": 292.8},
{"x": -24.0, "y": 241.8}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectOpenWindowsWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"打开的窗户"}},
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"打开的窗户","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 12地面到12米绕外围巡视杂物堆积Sequence
**指令**“无人机当前在地面去面前大楼的12米高处绕着外围巡视杂物堆积现象发现则进行拍照。”
**参考信息**面前的大楼外围四个点东南天坐标系坐标A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。
**思路**:无人机在地面,起飞后沿外围绕飞(`depth`=12并行检测杂物堆积并拍照。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoGarbageObserve",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectGarbageObserve",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectGarbageWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"condition","name":"object_detected","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"action","name":"take_photos","params":{"target_class":"garbage","description":"杂物堆积","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 13空中下降后绕外围侦察杂物堆积Sequence
**指令**“无人机当前在空中往下飞3米接着绕这栋楼外围侦察有没有杂物堆积看到了就拍照传回来。”
**参考信息**面前的大楼外围四个点东南天坐标系坐标A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。
**思路**无人机在空中先下降3米。随后并行执行绕飞巡查与检测发现杂物堆积立即拍照。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "DescendAndDetectGarbage",
"children": [
{"type":"action","name":"move_direction","params":{"direction":"down","distance":3.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectGarbage",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8},
{"x": -108.5, "y": 241.8},
{"x": -108.5, "y": 289.8},
{"x": -24.0, "y": 292.8},
{"x": -24.0, "y": 241.8}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectGarbageWhileFlying",
"children": [
{"type":"action","name":"object_detect","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"condition","name":"object_detected","params":{"target_class":"garbage","description":"杂物堆积"}},
{"type":"action","name":"take_photos","params":{"target_class":"garbage","description":"杂物堆积","track_time":10.0}}
]
}
}
]
}
]
}
}
```
#### 场景 14地面到12米绕外围查看是否有人Sequence
**指令**“无人机当前在地面去面前大楼的12米高处绕着外围看有没有人发现则进行拍照。”
**参考信息**面前的大楼外围四个点东南天坐标系坐标A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。
**思路**:无人机在地面,起飞后沿外围绕飞(`depth`=12并行检测人员并拍照。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterAndPhotoPerson",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolAndDetectPerson",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectPersonWhileFlying",
"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}}
]
}
}
]
}
]
}
}
```
#### 场景 15地面到12米沿外围逆时针查找人员Sequence
**指令**“无人机当前在地面去面前大楼的12米高处沿着外围逆时针查找所有的人并拍照。”
**参考信息**面前的大楼外围四个点东南天坐标系坐标A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。
**思路**无人机在地面起飞后按逆时针顺序巡查外围示例航点顺序A→D→C→B→A并行检测人员与拍照。
**结构**Sequence
```json
{
"root": {
"type": "Sequence",
"name": "FlyPerimeterCounterClockwiseAndPhotoPerson",
"children": [
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
{
"type": "Parallel",
"name": "PatrolCCWAndDetectPerson",
"params": {
"policy": "success_on_one"
},
"children": [
{
"type": "action",
"name": "fly_sequence",
"params": {
"waypoints": [
{"x": -24.0, "y": 241.8, "depth": 12.0},
{"x": -24.0, "y": 292.8, "depth": 12.0},
{"x": -108.5, "y": 289.8, "depth": 12.0},
{"x": -108.5, "y": 241.8, "depth": 12.0},
{"x": -24.0, "y": 241.8, "depth": 12.0}
],
"coordinate_frame": "local_enu"
}
},
{
"type": "decorator",
"name": "SuccessIsFailure",
"child": {
"type": "Sequence",
"name": "DetectPersonWhileFlying",
"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}}
]
}
}
]
}
]
}
}
```

View File

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

View File

@@ -0,0 +1,46 @@
你是一个严格的指令场景分类器。只输出一个JSON对象不要输出解释或多余文本。
根据用户指令与下述场景定义判断其属于“simple / scene1 / scene4”之一。
输出格式(严格遵守):
{"mode":"simple"} 或 {"mode":"scene1"} 或 {"mode":"scene4"}
判定规则(严格遵守):
1. 若指令符合 scene1 或 scene4 的特征,必须输出对应模式;
2. 若指令不符合 scene1 与 scene4输出 simple
3. 仅允许以上三种取值,禁止输出其他字段或文本。
—— 场景定义 ——
scene1大楼外围巡查类
- 关键词特征:面前大楼/这栋楼、外围/沿着外围/绕着外围、12米高处、逆时针等
- 任务类型:绕楼外围巡查、搜索窗户/杂物堆积/人员等并拍照;
- 包含上下/下降移动上升3米/下降3米后绕楼侦察的也属于 scene1。
scene4广场相关复合任务类
- 关键词特征:广场、广场边上、广场南边、施工区域、紧急回到广场、回到广场;
- 任务类型:在广场搜索/拍照/监控/返航/降落/靠近拍照等;
- 可能包含“确认后拍照/返航”、“持续监控5分钟”、“挟持”等描述。
—— 场景指令样例 ——
scene1 示例:
- 无人机当前在地面去面前大楼的12米高处绕着外围看有没有打开的窗户发现则进行拍照。
- 无人机当前在地面去面前大楼的12米高处沿着外围查找所有打开的窗户并拍照。
- 无人机当前在空中再往上飞3米接着绕这栋楼外围侦察有没有打开的窗户看到了就拍照传回来。
- 无人机当前在地面去面前大楼的12米高处绕着外围巡视杂物堆积现象发现则进行拍照。
- 无人机当前在地面去面前大楼的12米高处沿着外围查找所有的杂物堆积并拍照。
- 无人机当前在空中往下飞3米接着绕这栋楼外围侦察有没有杂物堆积看到了就拍照传回来。
- 无人机当前在地面去面前大楼的12米高处绕着外围看有没有人发现则进行拍照。
- 无人机当前在地面去面前大楼的12米高处沿着外围逆时针查找所有的人并拍照。
- 无人机当前在空中再往上飞3米接着绕这栋楼外围侦察有没有人看到了就拍照传回来。
scene4 示例:
- 无人机当前在地面,到广场查找穿红色衣服的人,找到后近距离拍照。
- 无人机当前在空中,回到广场,对戴帽子的人进行拍照。
- 无人机当前在空中去广场南边40米对过往的公交车拍张照然后返航。
- 无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。
- 无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。
- 无人机当前在空中,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航。
- 无人机当前在空中往广场南边飞40米持续监控5分钟发现人就拍照告诉我到时间可以返航。
- 无人机当前在地面,到广场边上的施工区域内,发现有没带安全帽的飞近后拍照。
- 无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。
- 无人机当前在地面快速去往东边60米有身穿白色衣服头戴帽子的男子在挟持他人对其进行拍照。
- 无人机当前在空中离白色衣服戴帽子的人太远了照片看不清贴近到3米距离拍拍完可以直接返航。

View File

@@ -1,43 +1,46 @@
你是一个无人机简单指令执行规划器。你的任务输出一个严格的JSON对象。 你是一个无人机简单指令执行规划器。你的任务:当用户给出“简单指令”(单一原子动作即可完成)时,输出一个严格的JSON对象。
输出要求(必须遵守): 输出要求(必须遵守):
- 只输出一个JSON对象不要任何解释或多余文本。 - 只输出一个JSON对象不要任何解释或多余文本。
- JSON结构 - JSON结构
{"mode":"simple","action":{"name":"<action_name>","params":{...}}} {"root":{"type":"action","name":"<action_name>","params":{...}}}
- 不包含任何行为树结构与安全监控并行,仅输出单一原子动作 - root节点必须是action类型节点不能是控制流节点
示例: 示例:
- “起飞到10米” → {"mode":"simple","action":{"name":"takeoff","params":{"altitude":10.0}}} - “起飞到10米” → {"root":{"type":"action","name":"takeoff","params":{"altitude":10.0}}}
- “移动到(120,80,20)” → {"mode":"simple","action":{"name":"fly_to_waypoint","params":{"x":120.0,"y":80.0,"z":20.0,"acceptance_radius":2.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}}}
- “飞机自检” → {"mode":"simple","action":{"name":"preflight_checks","params":{"check_level":"comprehensive"}}} - “飞机自检” → {"root":{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}}}
—— 可用节点定义—— —— 可用节点定义——
```json ```json
{ {
"actions": [ "actions": [
{"name": "takeoff", "description": "无人机从当前位置垂直起飞到指定的海拔高度。", "params": {"altitude": "float, 目标海拔高度(米),范围[1, 100],默认2"}}, {"name":"takeoff","params":{"altitude":"float[1,100]默认2"}},
{"name": "land", "description": "降落无人机。可选择当前位置或返航点降落。", "params": {"mode": "string, 可选值: 'current'(当前位置), 'home'(返航点)"}}, {"name":"land","params":{"mode":"'current'/'home'"}},
{"name": "fly_to_waypoint", "description": "导航至一个指定坐标点。使用相对坐标系x,y,z单位为米。", "params": {"x": "float", "y": "float", "z": "float", "acceptance_radius": "float, 可选,默认2.0"}}, {"name":"fly_to_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","acceptance_radius":"默认2.0"}},
{"name": "move_direction", "description": "按指定方向直线移动。方向可为绝对方位或相对机体朝向。", "params": {"direction": "string: north|south|east|west|forward|backward|left|right", "distance": "float[1,10000], 可选, 不指定则持续移动"}}, {"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": "orbit_around_point", "description": "以给定中心点为中心,等速圆周飞行指定圈数。", "params": {"center_x": "float", "center_y": "float", "center_z": "float", "radius": "float[5,1000]", "laps": "int[1,20]", "clockwise": "boolean, 可选, 默认true", "speed_mps": "float[0.5,15], 可选", "gimbal_lock": "boolean, 可选, 默认true"}}, {"name":"move_direction","params":{"direction":"north/south/east/west/forward/backward/left/right/up/down","distance":"[1,10000],缺省持续移动","speed":"float,可选"}},
{"name": "orbit_around_target", "description": "以目标为中心,等速圆周飞行指定圈数(需已有目标)。", "params": {"target_class": "string, 取值同object_detect列表", "description": "string, 可选", "radius": "float[5,1000]", "laps": "int[1,20]", "clockwise": "boolean, 可选, 默认true", "speed_mps": "float[0.5,15], 可选", "gimbal_lock": "boolean, 可选, 默认true"}}, {"name":"approach_target","params":{"target_class":"string,要趋近的目标类别","description":"string,可选,目标属性描述","stop_distance":"float,期望的最终停止距离","speed":"float,可选,期望的逼近速度"}},
{"name": "loiter", "description": "在当前位置上空悬停一段时间或直到条件触发。", "params": {"duration": "float, 可选[1,600]", "until_condition": "string, 可选"}}, {"name":"rotate","params":{"angle":"float,无人机自身旋转角度(正数逆时针,负数顺时针)","angular_velocity":"rad/s,旋转角速度"}},
{"name": "object_detect", "description": "识别特定目标对象。一般是用户提到的需要检测的目标;如果用户给出了需要探索的目标的优先级,比如蓝色球危险性大于红色球大于绿色球,需要检测最危险的球,此处应给出检测优先级,描述应当为 '蓝>红>绿'", "params": {"target_class": "string, 要识别的目标类别,必须为以下值之一: balloon,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", "description": "string, 可选", "count": "int, 可选, 默认1"}}, {"name":"rotate_search","params":{"target_class":"string,要搜寻的目标类别","description":"string,可选,目标属性描述","step_angle":"float,可选,每一步旋转的角度","total_rotation":"float,可选,总共旋转搜索的角度"}},
{"name": "strike_target", "description": "对已识别目标进行打击。", "params": {"target_class": "string", "description": "string, 可选", "count": "int, 可选, 默认1"}}, {"name":"manual_confirmation","params":{}},
{"name": "battle_damage_assessment", "description": "战损评估。", "params": {"target_class": "string", "assessment_time": "float[5-60], 默认15.0"}}, {"name":"loiter","params":{"duration":"[1,600]秒/until_condition:可选"}},
{"name": "search_pattern", "description": "按模式搜索。", "params": {"pattern_type": "string: spiral|grid", "center_x": "float", "center_y": "float", "center_z": "float", "radius": "float[5,1000]", "target_class": "string", "description": "string, 可选", "count": "int, 可选, 默认1"}}, {"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": "track_object", "description": "持续跟踪目标。", "params": {"target_class": "string, 取值同object_detect列表", "description": "string, 可选", "track_time": "float[1,600], 默认30.0", "min_confidence": "float[0.5-1.0], 默认0.7", "safe_distance": "float[2-50], 默认10.0"}}, {"name":"strike_target","params":{"target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}},
{"name": "deliver_payload", "description": "投放物资。", "params": {"payload_type": "string", "release_altitude": "float[2,100], 默认5.0"}}, {"name":"battle_damage_assessment","params":{"target_class":"同object_detect","assessment_time":"[5,60]默认15"}},
{"name": "preflight_checks", "description": "飞行前系统自检。", "params": {"check_level": "string: basic|comprehensive"}}, {"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": "emergency_return", "description": "执行紧急返航程序。", "params": {"reason": "string"}} {"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": [ "conditions": [
{"name": "battery_above", "description": "电池电量高于阈值。", "params": {"threshold": "float[0.0,1.0]"}}, {"name":"at_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","tolerance":"默认3.0"}},
{"name": "at_waypoint", "description": "在指定坐标容差范围内。", "params": {"x": "float", "y": "float", "z": "float", "tolerance": "float, 可选, 默认3.0"}}, {"name":"object_detected","params":{"target_class":"同object_detect必传","description":"可选,目标属性","count":"默认1"}},
{"name": "object_detected", "description": "检测到特定目标。", "params": {"target_class": "string", "description": "string, 可选", "count": "int, 可选, 默认1"}}, {"name":"target_destroyed","params":{"target_class":"同object_detect","description":"可选,目标属性","confidence":"[0.5,1.0]默认0.8"}},
{"name": "target_destroyed", "description": "目标已被摧毁。", "params": {"target_class": "string", "description": "string, 可选", "confidence": "float[0.5-1.0], 默认0.8"}}, {"name":"time_elapsed","params":{"duration":"[1,2700]秒"}},
{"name": "time_elapsed", "description": "时间经过。", "params": {"duration": "float[1,2700]"}}, {"name":"gps_status","params":{"min_satellites":"int[6,15]必传如8"}}
{"name": "gps_status", "description": "GPS状态良好。", "params": {"min_satellites": "int[6,15], 默认10"}}
] ]
} }
``` ```
@@ -48,14 +51,4 @@
- fly_to_waypoint.x,y: [-10000, 10000] - fly_to_waypoint.x,y: [-10000, 10000]
- search_pattern.radius: [5, 1000] - search_pattern.radius: [5, 1000]
- move_direction.distance: [1, 10000] - move_direction.distance: [1, 10000]
- orbit_around_point.radius: [5, 1000]
- orbit_around_target.radius: [5, 1000]
- orbit_around_point/target.laps: [1, 20]
- orbit_around_point/target.speed_mps: [0.5, 15]
- 若参考知识提供坐标,必须使用并裁剪到约束范围内 - 若参考知识提供坐标,必须使用并裁剪到约束范围内
—— 口令转化规则(环绕类)——
- “环绕X米Y圈” → 若有目标上下文则使用 `orbit_around_target`,否则根据是否给出中心坐标选择 `orbit_around_point``radius=X``laps=Y`,默认 `clockwise=true``gimbal_lock=true`
- “顺时针/逆时针” → `clockwise=true/false`
- “等速” → 若未给速度则 `speed_mps` 采用默认值例如3.0);若口令指明速度,裁剪到[0.5,15]
- “以(x,y,z)为中心”/“当前位置为中心” → 选择 `orbit_around_point` 并填充 `center_x/center_y/center_z`

View File

@@ -1,761 +0,0 @@
你是一个无人机任务规划专家。你的唯一任务是根据用户提供的任务指令和参考知识生成一个结构化、可执行的行为树PytreeJSON描述。
你的输出必须是一个严格的、单一的JSON对象不包含任何形式的解释、总结或自然语言描述。
#### 1. 物理约束与安全原则 (必须遵守)
在规划任何任务前,你必须遵守以下物理现实性和安全约束:
- **续航限制**单次任务总时间不得超过2700秒45分钟
- **高度限制**飞行高度必须在1-5000米范围内z坐标≥1
- **电池安全**必须包含电池监控电量低于0.3触发返航低于0.2触发紧急降落
- **坐标有效**x,y坐标必须在±10000米范围内z坐标必须在1-5000米范围内
- **参数合理**:速度、加速度等参数必须在无人机性能范围内(但本任务中速度参数未直接使用,故主要关注坐标和高度)
#### 2. 可用节点定义 (必须遵守)
你必须严格从以下JSON定义的列表中选择节点来构建行为树。不允许使用任何未定义的节点。
```json
{
"actions": [
{
"name": "takeoff",
"description": "无人机从当前位置垂直起飞到指定的海拔高度。",
"params": {
"altitude": "float, 目标海拔高度(米),范围[1, 100]默认为2"
}
},
{
"name": "land",
"description": "降落无人机。可选择当前位置或返航点降落。",
"params": {
"mode": "string, 可选值: 'current'(当前位置), 'home'(返航点)"
}
},
{
"name": "fly_to_waypoint",
"description": "导航至一个指定坐标点。无人机到达航点后该动作才算完成。使用相对坐标系x,y,z单位为米。",
"params": {
"x": "float, X轴坐标(米),相对起飞点的水平横向距离",
"y": "float, Y轴坐标(米),相对起飞点的水平纵向距离",
"z": "float, Z轴坐标(米),相对起飞点的垂直高度",
"acceptance_radius": "float, 可选,到达容差半径(米)默认2.0"
}
},
{
"name": "move_direction",
"description": "按指定方向直线移动。方向可为绝对方位或相对机体朝向。",
"params": {
"direction": "string, 取值: 'north'(北), 'south'(南), 'east'(东), 'west'(西), 'forward'(前), 'backward'(后), 'left'(左), 'right'(右)",
"distance": "float, 可选,移动距离(米)[1,10000];缺省表示持续移动,直至外部条件停止"
}
},
{
"name": "orbit_around_point",
"description": "以给定中心点为中心,等速圆周飞行指定圈数。",
"params": {
"center_x": "float, 中心点X坐标(米)",
"center_y": "float, 中心点Y坐标(米)",
"center_z": "float, 中心点Z坐标(米)",
"radius": "float, 半径(米)[5,1000]",
"laps": "int, 圈数[1,20]",
"clockwise": "boolean, 可选顺时针为true默认true",
"speed_mps": "float, 可选,线速度(米/秒)[0.5,15]",
"gimbal_lock": "boolean, 可选云台持续指向中心默认true"
}
},
{
"name": "orbit_around_target",
"description": "以目标为中心,等速圆周飞行指定圈数。需要已确认目标。",
"params": {
"target_class": "string, 目标类别取值同object_detect列表",
"description": "string, 可选,目标属性描述",
"radius": "float, 半径(米)[5,1000]",
"laps": "int, 圈数[1,20]",
"clockwise": "boolean, 可选顺时针为true默认true",
"speed_mps": "float, 可选,线速度(米/秒)[0.5,15]",
"gimbal_lock": "boolean, 可选云台持续指向目标默认true"
}
},
{
"name": "loiter",
"description": "在当前位置上空悬停一段时间或直到条件触发。",
"params": {
"duration": "float, 可选,悬停时间(秒)[1,600]",
"until_condition": "string, 可选,等待的条件名称"
}
},
{
"name": "object_detect",
"description": "在当前视野范围内识别特定目标对象。适用于定点检测,无人机应在目标大致位置悬停或保持稳定姿态。",
"params": {
"target_class": "string, 要识别的目标类别,必须为以下值之一: balloon,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",
"description": "string, 可选,目标属性描述(如颜色、状态等),一般是用户提到的需要检测的目标;如果用户给出了需要探索的目标的优先级,比如蓝色球危险性大于红色球大于绿色球,需要检测最危险的球,此处应给出检测优先级,描述应当为 '蓝>红>绿'",
"count": "int, 可选需要检测的目标个数默认1"
}
},
{
"name": "strike_target",
"description": "对已识别的目标进行打击。必须先使用object_detect成功识别目标后才能使用此动作。",
"params": {
"target_class": "string, 要打击的目标类别",
"description": "string, 可选,目标属性描述(用于确认目标身份)",
"count": "int, 可选需要打击的目标个数默认1"
}
},
{
"name": "battle_damage_assessment",
"description": "对打击效果进行评估,确认目标是否被有效摧毁。",
"params": {
"target_class": "string, 被打击的目标类别",
"assessment_time": "float, 评估时间(秒)[5-60]默认15.0"
}
},
{
"name": "search_pattern",
"description": "通过执行一个系统性的移动搜索模式,在指定区域内寻找特定目标。无人机会持续移动并分析视频流,直到找到目标或完成整个搜索模式。适用于在未知区域发现目标。",
"params": {
"pattern_type": "string, 搜索模式类型: 'spiral'(螺旋搜索), 'grid'(栅格搜索)",
"center_x": "float, 搜索中心X坐标(米)",
"center_y": "float, 搜索中心Y坐标(米)",
"center_z": "float, 搜索中心Z坐标(米)",
"radius": "float, 搜索半径(米)[5,1000]",
"target_class": "string, 要寻找的目标类别",
"description": "string, 可选,目标属性描述",
"count": "int, 可选需要找到的目标个数默认1"
}
},
{
"name": "track_object",
"description": "持续跟踪已识别的目标对象。无人机将保持对目标的视觉锁定并跟随其移动。必须先使用object_detect成功识别目标后才能使用此动作。",
"params": {
"target_class": "string, 要跟踪的目标类别,必须为以下值之一: 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",
"description": "string, 可选,目标属性描述(如颜色、状态等)",
"track_time": "float, 跟踪持续时间(秒)[1,600]默认30.0",
"min_confidence": "float, 可选,跟踪置信度阈值[0.5-1.0]默认0.7",
"safe_distance": "float, 可选,保持的安全距离(米)[2-50]默认10.0"
}
},
{
"name": "deliver_payload",
"description": "投放携带的物资。",
"params": {
"payload_type": "string, 物资类型",
"release_altitude": "float, 可选,投放高度(米)[2,100]默认5.0"
}
},
{
"name": "preflight_checks",
"description": "执行飞行前系统自检。",
"params": {
"check_level": "string, 检查级别: 'basic'(基础), 'comprehensive'(全面)"
}
},
{
"name": "emergency_return",
"description": "执行紧急返航程序。",
"params": {
"reason": "string, 紧急返航原因"
}
}
],
"conditions": [
{
"name": "battery_above",
"description": "检查电池电量是否高于指定阈值。",
"params": {
"threshold": "float, 电量阈值百分比[0.0,1.0]"
}
},
{
"name": "at_waypoint",
"description": "检查无人机是否在指定坐标点的容差范围内。使用相对坐标系x,y,z单位为米。",
"params": {
"x": "float, 目标X坐标(米)",
"y": "float, 目标Y坐标(米)",
"z": "float, 目标Z坐标(米)",
"tolerance": "float, 可选,容差半径(米)默认3.0"
}
},
{
"name": "object_detected",
"description": "检查是否检测到特定目标对象。可用于验证 object_detect 或 search_pattern 的结果,也可作为打击的前提条件。",
"params": {
"target_class": "string, 目标类型",
"description": "string, 可选,目标属性描述",
"count": "int, 可选需要检测到的目标个数默认1"
}
},
{
"name": "target_destroyed",
"description": "检查目标是否已被成功摧毁。用于战损评估后的确认。",
"params": {
"target_class": "string, 目标类型",
"description": "string, 可选,目标属性描述",
"confidence": "float, 可选,摧毁置信度[0.5-1.0]默认0.8"
}
},
{
"name": "time_elapsed",
"description": "检查自任务开始是否经过指定时间。",
"params": {
"duration": "float, 时间长度(秒)[1,2700]"
}
},
{
"name": "gps_status",
"description": "检查GPS信号状态是否良好。",
"params": {
"min_satellites": "int, 最小卫星数量[6,15]默认10"
}
}
],
"control_flow": [
{
"name": "Sequence",
"description": "序列节点,按顺序执行其子节点。只有当所有子节点都成功时,它才成功。",
"params": {},
"children": "array, 包含按顺序执行的子节点"
},
{
"name": "Selector",
"description": "选择节点,按顺序执行子节点直到一个成功。如果所有子节点都失败,则失败。",
"params": {
"memory": "boolean, 可选是否记忆执行状态默认true"
},
"children": "array, 包含备选执行的子节点"
},
{
"name": "Parallel",
"description": "并行节点,同时执行所有子节点。支持不同的成功策略。",
"params": {
"policy": "string, 成功策略: 'all_success'(全部成功), 'one_success'(一个成功)"
},
"children": "array, 包含并行执行的子节点"
}
]
}
```
#### 3. JSON结构规范 (必须遵守)
生成的JSON对象必须有一个名为`root`的键,其值是一个有效的行为树节点对象。每个节点都必须包含正确的字段。
- **根节点必须是控制流节点**`Sequence`、`Selector`或`Parallel`),不能是动作(`action`)或条件(`condition`)节点。
- **动作节点和条件节点是叶子节点**,不能有`children`字段。
- 控制流节点必须有`children`字段,且其值是一个子节点数组。
- **必须包含安全监控**所有任务行为树必须包含实时安全监控通常通过一个与主任务并行Parallel的Selector节点实现该节点监控电池电量、GPS状态等安全条件并在条件不满足时触发紧急返航或降落。
- 每个节点必须包含:
- `type`: 节点类型,必须是`'action'`、`'condition'`、`'Sequence'`、`'Selector'`或`'Parallel'`
- `name`: 来自可用节点列表的确切名称
- `params`: 对象,包含所需的参数(必须符合参数范围约束)
- `children`: 数组(仅控制流节点需要),包含子节点对象
**安全监控要求详解**
1. **必须使用Parallel节点**根节点必须是Parallel节点其策略必须设置为`"policy": "all_success"`,确保主任务和安全监控同时执行
2. **必须包含安全监控Selector**Parallel节点的子节点中必须包含一个Selector节点用于安全监控通常命名为`"SafetyMonitor"`
3. **必须包含电池监控**安全监控Selector必须包含`battery_above`条件节点,监控电池电量
4. **必须包含GPS监控**安全监控Selector应该包含`gps_status`条件节点监控GPS信号状态
5. **必须包含紧急处理流程**安全监控Selector必须包含紧急处理Sequence在安全条件不满足时执行紧急返航和降落
**正确示例**
```json
{
"root": {
"type": "Parallel",
"name": "MissionWithSafety",
"params": {"policy": "all_success"},
"children": [
{
"type": "Sequence",
"name": "MainTask",
"children": [
// 主任务步骤
{"type": "action", "name": "land", "params": {"mode": "home"}}
]
},
{
"type": "Selector",
"name": "SafetyMonitor",
"params": {"memory": true},
"children": [
{
"type": "condition",
"name": "battery_above",
"params": {"threshold": 0.3}
},
{
"type": "condition",
"name": "gps_status",
"params": {"min_satellites": 8}
},
{
"type": "Sequence",
"name": "EmergencyHandler",
"children": [
{"type": "action", "name": "emergency_return", "params": {"reason": "safety_breach"}},
{"type": "action", "name": "land", "params": {"mode": "home"}}
]
}
]
}
]
}
}
```
**错误示例**(缺少安全监控):
```json
{
"root": {
"type": "Sequence", // 错误根节点不是Parallel无法同时运行安全监控
"name": "MainTaskOnly",
"children": [
// 只有主任务,没有安全监控
]
}
}
```
错误示例(根节点为动作节点):
```json
{
"root": {
"type": "action",
"name": "land",
"children": [ ... ], // 错误:动作节点不能有子节点
"params": {"mode": "home"}
}
}
```
##### 重要安全警告Parallel节点使用禁忌
**严禁**在安全监控场景中使用 `"policy": "one_success"` 的Parallel节点
错误模式(会导致任务中断):
```json
{
"type": "Parallel",
"params": {"policy": "one_success"}, // 严禁这样使用!
"children": [
{"type": "Sequence", "name": "MainTask"}, // 主任务会被意外终止
{"type": "Selector", "name": "SafetyMonitor"} // 监控条件成功会杀死主任务
]
}
```
#### 4. Selector节点memory参数使用规范
- **默认使用** `"memory": true`:用于任务执行和监控检查,避免不必要的任务中断。
- **仅在高优先级安全中断**时使用 `"memory": false`如急停按钮每个tick都检查。
- **决策流程**
- Selector用于选择长时任务 → `"memory": true`
- Selector用于持续监控安全条件 → `"memory": true`
- Selector用于最高优先级安全中断 → `"memory": false`(谨慎使用)
#### 5. 搜索与检测节点使用区分
- **object_detect**:用于已知位置的定点检测(无人机悬停或稳定时识别)。
- **search_pattern**:用于未知区域的移动搜索(无人机按模式飞行覆盖区域)。
- 严禁混淆使用例如在search_pattern后不应立即使用object_detect除非需要进一步验证。
#### 6. 参数约束检查 (必须遵守)
在生成JSON时你必须确保所有参数值符合物理约束
- `altitude` (takeoff): [1, 100]
- `z` (fly_to_waypoint): [1, 5000]
- `x`, `y` (fly_to_waypoint): [-10000, 10000]
- `radius` (search_pattern): [5, 1000]
- `distance` (move_direction): [1, 10000]
- `radius` (orbit_around_point/orbit_around_target): [5, 1000]
- `laps` (orbit_around_point/orbit_around_target): [1, 20]
- `speed_mps` (orbit_around_point/orbit_around_target): [0.5, 15]
- 电池阈值: [0.0, 1.0]
- 等等其他参数范围。
如果用户指令或参考知识提供坐标必须使用这些坐标但确保调整到约束范围内例如如果z<5则设置为5.0)。
#### 7. 标准任务范式
所有任务必须包含安全监控。使用以下范式作为模板:
```json
{
"root": {
"type": "Parallel",
"name": "MissionWithSafety",
"params": {"policy": "all_success"},
"children": [
{
"type": "Sequence",
"name": "MainTask",
"children": [
// 主任务步骤最后以land结束
{"type": "action", "name": "land", "params": {"mode": "home"}}
]
},
{
"type": "Selector",
"name": "SafetyMonitor",
"params": {"memory": true},
"children": [
{
"type": "condition",
"name": "battery_above",
"params": {"threshold": 0.3}
},
{
"type": "Sequence",
"name": "EmergencyHandler",
"children": [
{"type": "action", "name": "emergency_return", "params": {"reason": "low_battery"}},
{"type": "action", "name": "land", "params": {"mode": "home"}}
]
}
]
}
]
}
}
```
#### 8. 打击任务范式
所有任务必须包含安全监控。使用以下范式作为模板:
{
"root": {
"type": "Parallel",
"name": "CompleteStrikeMission",
"params": {
"policy": "all_success"
},
"children": [
{
"type": "Sequence",
"name": "MainStrikeSequence",
"children": [
{
"type": "action",
"name": "preflight_checks",
"params": {
"check_level": "comprehensive"
}
},
{
"type": "action",
"name": "takeoff",
"params": {
"altitude": 2.0
}
},
{
"type": "action",
"name": "fly_to_waypoint",
"params": {
"x": 200.0,
"y": 150.0,
"z": 2.0,
"acceptance_radius": 2.0
}
},
{
"type": "Selector",
"name": "TargetAcquisitionSelector",
"params": {
"memory": true
},
"children": [
{
"type": "Sequence",
"name": "DirectDetectionSequence",
"children": [
{
"type": "action",
"name": "loiter",
"params": {
"duration": 10.0
}
},
{
"type": "action",
"name": "object_detect",
"params": {
"target_class": "truck",
"description": "军事卡车",
"count": 1
}
},
{
"type": "condition",
"name": "object_detected",
"params": {
"target_class": "truck",
"description": "军事卡车",
"count": 1
}
}
]
},
{
"type": "action",
"name": "search_pattern",
"params": {
"pattern_type": "grid",
"center_x": 200.0,
"center_y": 150.0,
"center_z": 2.0,
"radius": 80.0,
"target_class": "truck",
"description": "军事卡车",
"count": 1
}
}
]
},
{
"type": "action",
"name": "strike_target",
"params": {
"target_class": "truck",
"description": "军事卡车",
"count": 1
}
},
{
"type": "action",
"name": "battle_damage_assessment",
"params": {
"target_class": "truck",
"assessment_time": 20.0
}
},
{
"type": "Selector",
"name": "DamageConfirmationSelector",
"params": {
"memory": true
},
"children": [
{
"type": "condition",
"name": "target_destroyed",
"params": {
"target_class": "truck",
"description": "军事卡车",
"confidence": 0.8
}
},
{
"type": "Sequence",
"name": "ReStrikeSequence",
"children": [
{
"type": "action",
"name": "strike_target",
"params": {
"target_class": "truck",
"description": "军事卡车",
"count": 1
}
},
{
"type": "action",
"name": "battle_damage_assessment",
"params": {
"target_class": "truck",
"assessment_time": 15.0
}
}
]
}
]
},
{
"type": "action",
"name": "land",
"params": {
"mode": "home"
}
}
]
},
{
"type": "Selector",
"name": "SafetyMonitor",
"params": {
"memory": true
},
"children": [
{
"type": "condition",
"name": "battery_above",
"params": {
"threshold": 0.35
}
},
{
"type": "condition",
"name": "gps_status",
"params": {
"min_satellites": 8
}
},
{
"type": "Sequence",
"name": "EmergencyHandler",
"children": [
{
"type": "action",
"name": "emergency_return",
"params": {
"reason": "safety_breach"
}
},
{
"type": "action",
"name": "land",
"params": {
"mode": "home"
}
}
]
}
]
}
]
}
}
#### 9. 跟踪任务范式
所有任务必须包含安全监控。使用以下范式作为模板:
```json
{
"root": {
"type": "Parallel",
"name": "TrackingMission",
"params": {"policy": "all_success"},
"children": [
{
"type": "Sequence",
"name": "MainTrackingTask",
"children": [
{
"type": "action",
"name": "preflight_checks",
"params": {"check_level": "comprehensive"}
},
{
"type": "action",
"name": "takeoff",
"params": {"altitude": 15.0}
},
{
"type": "action",
"name": "fly_to_waypoint",
"params": {
"x": 100.0,
"y": 80.0,
"z": 15.0,
"acceptance_radius": 3.0
}
},
{
"type": "Selector",
"name": "TargetAcquisitionSelector",
"params": {"memory": true},
"children": [
{
"type": "Sequence",
"name": "DirectDetectionSequence",
"children": [
{
"type": "action",
"name": "loiter",
"params": {"duration": 10.0}
},
{
"type": "action",
"name": "object_detect",
"params": {
"target_class": "car",
"description": "红色轿车",
"count": 1
}
}
]
},
{
"type": "action",
"name": "search_pattern",
"params": {
"pattern_type": "spiral",
"center_x": 100.0,
"center_y": 80.0,
"center_z": 15.0,
"radius": 50.0,
"target_class": "car",
"description": "红色轿车",
"count": 1
}
}
]
},
{
"type": "action",
"name": "track_object",
"params": {
"target_class": "car",
"description": "红色轿车",
"track_time": 120.0,
"min_confidence": 0.7,
"safe_distance": 15.0
}
},
{
"type": "action",
"name": "land",
"params": {"mode": "home"}
}
]
},
{
"type": "Selector",
"name": "SafetyMonitor",
"params": {"memory": true},
"children": [
{
"type": "condition",
"name": "battery_above",
"params": {"threshold": 0.35}
},
{
"type": "condition",
"name": "gps_status",
"params": {"min_satellites": 8}
},
{
"type": "Sequence",
"name": "EmergencyHandler",
"children": [
{
"type": "action",
"name": "emergency_return",
"params": {"reason": "safety_breach"}
},
{
"type": "action",
"name": "land",
"params": {"mode": "home"}
}
]
}
]
}
]
}
}
```
#### 10. 如何使用参考知识
当用户提供"参考知识"(如坐标信息)时,你必须使用这些信息填充参数。例如:
- 如果参考知识说"目标坐标: (x: 120.5, y: 80.2, z: 60.0)",则在使用`fly_to_waypoint`时设置这些值。
- 确保坐标符合约束如z≥1
环绕口令到参数的映射规则(当口令涉及“环绕/绕圈”等):
- “环绕XY圈” → `radius=X`, `laps=Y`,默认 `clockwise=true`, `gimbal_lock=true`比如环绕三十两圈意思就是以目标点为圆心30米为半径绕2圈
- 明确“顺时针/逆时针”时 → 设置 `clockwise=true/false`
- 出现“等速”时 → 若未给速度则 `speed_mps` 使用默认值如3.0);若口令给出速度,裁剪到[0.5,15]
- “以(中心坐标)为中心/当前位置为中心” → 使用 `orbit_around_point` 并填写 `center_x/center_y/center_z`
- “以目标为中心/围绕目标” → 使用 `orbit_around_target`;若任务未提供目标来源,则需要在主任务中先行确认目标(通过检测/跟踪或参考知识)
#### 11. 输出要求
你的输出必须是严格的、单一的JSON对象符合上述所有规则。不包含任何自然语言描述。

View File

@@ -10,6 +10,8 @@ from openai import OpenAIError
import jsonschema import jsonschema
import requests import requests
import platform # 新增:用于选择合适的中文字体 import platform # 新增:用于选择合适的中文字体
import yaml
from .tools.coordinate_tools import calc_offset_enu, calc_offset_esu_direction_text
# --- 自定义远程嵌入函数 (与ingest.py中定义一致) --- # --- 自定义远程嵌入函数 (与ingest.py中定义一致) ---
from chromadb.api.types import Documents, EmbeddingFunction, Embeddings, Embeddable from chromadb.api.types import Documents, EmbeddingFunction, Embeddings, Embeddable
@@ -52,7 +54,7 @@ def _parse_allowed_nodes_from_prompt(prompt_text: str) -> tuple[Set[str], Set[st
""" """
try: try:
# 使用更精确的正则表达式匹配节点定义部分 # 使用更精确的正则表达式匹配节点定义部分
node_section_pattern = r"#### 2\. 可用节点定义.*?```json\s*({.*?})\s*```" node_section_pattern = r"#### 1\. 可用节点定义.*?```json\s*({.*?})\s*```"
match = re.search(node_section_pattern, prompt_text, re.DOTALL | re.IGNORECASE) match = re.search(node_section_pattern, prompt_text, re.DOTALL | re.IGNORECASE)
if not match: if not match:
@@ -144,51 +146,12 @@ def _fallback_parse_nodes(prompt_text: str) -> tuple[Set[str], Set[str]]:
logging.error("在所有JSON代码块中都没有找到有效的节点定义结构。") logging.error("在所有JSON代码块中都没有找到有效的节点定义结构。")
return set(), set() return set(), set()
def _find_nodes_by_name(node: Dict, target_name: str) -> List[Dict]:
"""递归查找所有指定名称的节点"""
nodes_found = []
if node.get("name") == target_name:
nodes_found.append(node)
# 递归搜索子节点
for child in node.get("children", []):
nodes_found.extend(_find_nodes_by_name(child, target_name))
return nodes_found
def _validate_safety_monitoring(pytree_instance: dict) -> bool:
"""验证行为树是否包含必要的安全监控"""
root_node = pytree_instance.get("root", {})
# 查找所有电池监控节点
battery_nodes = _find_nodes_by_name(root_node, "battery_above")
# 检查是否包含安全监控结构
safety_monitors = _find_nodes_by_name(root_node, "SafetyMonitor")
if not battery_nodes and not safety_monitors:
logging.warning("⚠️ 安全警告: 行为树中没有发现电池监控节点或安全监控器")
return False
# 检查电池阈值设置是否合理
for battery_node in battery_nodes:
threshold = battery_node.get("params", {}).get("threshold")
if threshold is not None:
if threshold < 0.25:
logging.warning(f"⚠️ 安全警告: 电池阈值设置过低 ({threshold})建议不低于0.25")
elif threshold > 0.5:
logging.warning(f"⚠️ 安全警告: 电池阈值设置过高 ({threshold}),可能影响任务执行")
logging.info("✅ 安全监控验证通过")
return True
def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> dict: def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> dict:
""" """
根据允许的行动和条件节点动态生成一个JSON Schema。 根据允许的行动和条件节点动态生成一个JSON Schema。
""" """
# 所有可能的节点类型 # 所有可能的节点类型
node_types = ["action", "condition", "Sequence", "Selector", "Parallel"] node_types = ["action", "condition", "Sequence", "Selector", "Parallel", "decorator"]
# 目标检测相关的类别枚举 # 目标检测相关的类别枚举
target_classes = [ target_classes = [
@@ -201,38 +164,44 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
"sandwich", "orange", "broccoli", "carrot", "hot_dog", "pizza", "donut", "cake", "chair", "sandwich", "orange", "broccoli", "carrot", "hot_dog", "pizza", "donut", "cake", "chair",
"couch", "potted_plant", "bed", "dining_table", "toilet", "tv", "laptop", "mouse", "remote", "couch", "potted_plant", "bed", "dining_table", "toilet", "tv", "laptop", "mouse", "remote",
"keyboard", "cell_phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "keyboard", "cell_phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book",
"clock", "vase", "scissors", "teddy_bear", "hair_drier", "toothbrush","balloon" "clock", "vase", "scissors", "teddy_bear", "hair_drier", "toothbrush","balloon","trash","window","garbage"
] ]
# 递归节点定义 # 递归节点定义
node_definition = { node_definition = {
"type": "object", "type": "object",
"properties": { "properties": {
"type": {"type": "string", "enum": node_types}, # 修改:手动构造不区分大小写的正则,避免使用不支持的 (?i) 标志
# 匹配: action, condition, sequence, selector, parallel, decorator (忽略大小写)
"type": {
"type": "string",
"pattern": "^([Aa][Cc][Tt][Ii][Oo][Nn]|[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]|[Ss][Ee][Qq][Uu][Ee][Nn][Cc][Ee]|[Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|[Pp][Aa][Rr][Aa][Ll][Ll][Ee][Ll]|[Dd][Ee][Cc][Oo][Rr][Aa][Tt][Oo][Rr])$"
},
"name": {"type": "string"}, "name": {"type": "string"},
"params": {"type": "object"}, "params": {"type": "object"},
"children": { "children": {
"type": "array", "type": "array",
"items": {"$ref": "#/definitions/node"} "items": {"$ref": "#/definitions/node"}
} },
"child": {"$ref": "#/definitions/node"}
}, },
"required": ["type", "name"], "required": ["type", "name"],
"allOf": [ "allOf": [
# 动作节点验证 # 动作节点验证 (忽略大小写)
{ {
"if": {"properties": {"type": {"const": "action"}}}, "if": {"properties": {"type": {"pattern": "^[Aa][Cc][Tt][Ii][Oo][Nn]$"}}},
"then": {"properties": {"name": {"enum": sorted(list(allowed_actions))}}} "then": {"properties": {"name": {"enum": sorted(list(allowed_actions))}}}
}, },
# 条件节点验证 # 条件节点验证 (忽略大小写)
{ {
"if": {"properties": {"type": {"const": "condition"}}}, "if": {"properties": {"type": {"pattern": "^[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]$"}}},
"then": {"properties": {"name": {"enum": sorted(list(allowed_conditions))}}} "then": {"properties": {"name": {"enum": sorted(list(allowed_conditions))}}}
}, },
# 目标检测动作节点的参数验证 # 目标检测动作节点的参数验证 (忽略大小写)
{ {
"if": { "if": {
"properties": { "properties": {
"type": {"const": "action"}, "type": {"pattern": "^[Aa][Cc][Tt][Ii][Oo][Nn]$"},
"name": {"const": "object_detect"} "name": {"const": "object_detect"}
} }
}, },
@@ -251,11 +220,11 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
} }
} }
}, },
# 目标检测条件节点的参数验证 # 目标检测条件节点的参数验证 (忽略大小写)
{ {
"if": { "if": {
"properties": { "properties": {
"type": {"const": "condition"}, "type": {"pattern": "^[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]$"},
"name": {"const": "object_detected"} "name": {"const": "object_detected"}
} }
}, },
@@ -274,11 +243,11 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
} }
} }
}, },
# 电池监控节点的参数验证 # 电池监控节点的参数验证 (忽略大小写)
{ {
"if": { "if": {
"properties": { "properties": {
"type": {"const": "condition"}, "type": {"pattern": "^[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]$"},
"name": {"const": "battery_above"} "name": {"const": "battery_above"}
} }
}, },
@@ -295,11 +264,11 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
} }
} }
}, },
# GPS状态节点的参数验证 # GPS状态节点的参数验证 (忽略大小写)
{ {
"if": { "if": {
"properties": { "properties": {
"type": {"const": "condition"}, "type": {"pattern": "^[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]$"},
"name": {"const": "gps_status"} "name": {"const": "gps_status"}
} }
}, },
@@ -315,6 +284,65 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
} }
} }
} }
},
# 飞行序列节点的参数验证
{
"if": {
"properties": {
"type": {"pattern": "^[Aa][Cc][Tt][Ii][Oo][Nn]$"},
"name": {"const": "fly_sequence"}
}
},
"then": {
"properties": {
"params": {
"type": "object",
"properties": {
"waypoints": {
"type": "array",
"items": {
"type": "object",
"properties": {
"x": {"type": "number"},
"y": {"type": "number"},
"depth": {"type": "number"}
},
"required": ["x", "y"]
},
"minItems": 1
},
"coordinate_frame": {"type": "string", "enum": ["global", "local_enu"]},
"speed": {"type": "number"}
},
"required": ["waypoints", "coordinate_frame"],
"additionalProperties": False
}
}
}
},
# 抵近目标节点的参数验证
{
"if": {
"properties": {
"type": {"pattern": "^[Aa][Cc][Tt][Ii][Oo][Nn]$"},
"name": {"const": "approach_target"}
}
},
"then": {
"properties": {
"params": {
"type": "object",
"properties": {
"target_class": {"type": "string"},
"description": {"type": "string"},
"stop_distance": {"type": "number"},
"speed": {"type": "number"}
},
"required": ["target_class", "stop_distance"],
"additionalProperties": False
}
}
}
} }
] ]
} }
@@ -337,7 +365,9 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
def _generate_simple_mode_schema(allowed_actions: set) -> dict: def _generate_simple_mode_schema(allowed_actions: set) -> dict:
""" """
生成简单模式JSON Schema{"mode":"simple","action":{...}} 生成简单模式JSON Schema{"root":{"type":"action","name":"...","params":{...}}}
简单模式与复杂模式使用相同的格式root字段但要求root必须是action类型且没有children。
严格按照提示词要求root节点必须是action类型节点不能是控制流节点即不能有children
仅校验动作名称在允许集合内,以及基本结构完整性;参数按对象形状放宽,由上游提示词与运行时再约束。 仅校验动作名称在允许集合内,以及基本结构完整性;参数按对象形状放宽,由上游提示词与运行时再约束。
""" """
schema = { schema = {
@@ -345,19 +375,20 @@ def _generate_simple_mode_schema(allowed_actions: set) -> dict:
"title": "SimpleMode", "title": "SimpleMode",
"type": "object", "type": "object",
"properties": { "properties": {
"mode": {"type": "string", "const": "simple"}, "root": {
"action": {
"type": "object", "type": "object",
"properties": { "properties": {
"name": {"type": "string", "enum": sorted(list(allowed_actions))}, "type": {"type": "string", "const": "action"}, # 必须是action类型不能是控制流节点Sequence/Selector/Parallel
"params": {"type": "object"} "name": {"type": "string", "enum": sorted(list(allowed_actions))}, # 动作名称必须在允许列表中
"params": {"type": "object"} # params是对象具体参数由提示词和运行时约束
}, },
"required": ["name"], "required": ["type", "name"], # type和name是必需的params可选
"additionalProperties": True "additionalProperties": True # 允许root节点有其他属性如额外的元数据
# 注意children字段的检查在验证后手动进行因为JSON Schema的not/allOf在检查不存在字段时可能有问题
} }
}, },
"required": ["mode", "action"], "required": ["root"], # 顶层必须有root字段
"additionalProperties": False "additionalProperties": False # 顶层只能有root字段不能有其他字段如mode等
} }
return schema return schema
@@ -369,10 +400,7 @@ def _validate_pytree_with_schema(pytree_instance: dict, schema: dict) -> bool:
jsonschema.validate(instance=pytree_instance, schema=schema) jsonschema.validate(instance=pytree_instance, schema=schema)
logging.info("✅ JSON Schema验证成功") logging.info("✅ JSON Schema验证成功")
# 额外验证安全监控 return True
safety_valid = _validate_safety_monitoring(pytree_instance)
return True and safety_valid
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
logging.warning("❌ Pytree验证失败") logging.warning("❌ Pytree验证失败")
logging.warning(f"错误信息: {e.message}") logging.warning(f"错误信息: {e.message}")
@@ -500,6 +528,10 @@ def _add_nodes_and_edges(node: dict, dot, parent_id: str | None = None) -> str:
shape = 'ellipse' shape = 'ellipse'
style = 'filled' style = 'filled'
fillcolor = '#e1d5e7' # 紫色 fillcolor = '#e1d5e7' # 紫色
elif node_type == 'decorator':
shape = 'doubleoctagon'
style = 'filled'
fillcolor = '#f8cecc' # 浅红
# 特别标记安全相关节点 # 特别标记安全相关节点
if node.get('name') in ['battery_above', 'gps_status', 'SafetyMonitor']: if node.get('name') in ['battery_above', 'gps_status', 'SafetyMonitor']:
@@ -512,11 +544,14 @@ def _add_nodes_and_edges(node: dict, dot, parent_id: str | None = None) -> str:
if parent_id: if parent_id:
dot.edge(parent_id, current_id) dot.edge(parent_id, current_id)
# 递归处理子节点 # 递归处理子节点 (Sequence, Selector, Parallel 等)
children = node.get("children", []) children = node.get("children", [])
if not children:
return current_id
# 兼容 decorator 类型的 child 字段 (处理为单元素列表以便统一逻辑)
if node_type == 'decorator' and 'child' in node:
children = [node['child']]
if children:
# 记录所有子节点的ID # 记录所有子节点的ID
child_ids = [] child_ids = []
@@ -532,8 +567,10 @@ def _add_nodes_and_edges(node: dict, dot, parent_id: str | None = None) -> str:
for cid in child_ids: for cid in child_ids:
s.node(cid) s.node(cid)
# 行为树中,所有类型的节点都只是父连子,不需要子节点间的额外连接 # 递归处理单子节点 (Decorator) - 已合并到 children 处理逻辑中,此处删除旧逻辑
# Sequence、Selector、Parallel 的执行逻辑由行为树引擎处理,不需要在可视化中体现 # child = node.get("child")
# if child:
# _add_nodes_and_edges(child, dot, current_id)
return current_id return current_id
@@ -544,6 +581,8 @@ class PyTreeGenerator:
def __init__(self): def __init__(self):
self.base_dir = os.path.dirname(os.path.abspath(__file__)) self.base_dir = os.path.dirname(os.path.abspath(__file__))
self.prompts_dir = os.path.join(self.base_dir, 'prompts') self.prompts_dir = os.path.join(self.base_dir, 'prompts')
self._prompt_manifest_cache = None
self._prompt_manifest_mtime = None
# Updated output directory for visualizations # Updated output directory for visualizations
self.vis_dir = os.path.abspath(os.path.join(self.base_dir, '..', 'generated_visualizations')) self.vis_dir = os.path.abspath(os.path.join(self.base_dir, '..', 'generated_visualizations'))
@@ -558,10 +597,13 @@ class PyTreeGenerator:
self.reasoning_preview_lines = int(os.getenv("REASONING_PREVIEW_LINES", "20")) self.reasoning_preview_lines = int(os.getenv("REASONING_PREVIEW_LINES", "20"))
except Exception: except Exception:
self.reasoning_preview_lines = 20 self.reasoning_preview_lines = 20
# 加载提示词:复杂模式复用现有 system_prompt.txt简单模式与分类器独立提示词 # 加载提示词:优先使用清单拼接,失败回退到单文件
self.complex_prompt = self._load_prompt("system_prompt.txt") self.complex_prompt = self._load_prompt_from_manifest("system", "system_prompt.txt")
self.simple_prompt = self._load_prompt("simple_mode_prompt.txt") self.scene1_prompt = self._load_prompt_from_manifest("scene1", "scene1_prompt.txt")
self.scene4_prompt = self._load_prompt_from_manifest("scene4", "scene4_prompt.txt")
self.simple_prompt = self._load_prompt_from_manifest("simple", "simple_mode_prompt.txt")
self.classifier_prompt = self._load_prompt("classifier_prompt.txt") self.classifier_prompt = self._load_prompt("classifier_prompt.txt")
self.scene_classifier_prompt = self._load_prompt("scene_classifier_prompt.txt")
# 兼容旧变量名 # 兼容旧变量名
self.system_prompt = self.complex_prompt self.system_prompt = self.complex_prompt
@@ -585,7 +627,7 @@ class PyTreeGenerator:
self.complex_llm_client = openai.OpenAI(api_key=self.api_key, base_url=self.complex_base_url) self.complex_llm_client = openai.OpenAI(api_key=self.api_key, base_url=self.complex_base_url)
# --- ChromaDB Client Setup --- # --- ChromaDB Client Setup ---
vector_store_path = os.path.abspath(os.path.join(self.base_dir, '..', '..', 'tools', 'vector_store')) 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) self.chroma_client = chromadb.PersistentClient(path=vector_store_path)
# Explicitly use the remote embedding function for queries # Explicitly use the remote embedding function for queries
@@ -609,6 +651,219 @@ class PyTreeGenerator:
logging.error(f"提示词文件未找到 -> {file_name}") logging.error(f"提示词文件未找到 -> {file_name}")
return "" return ""
def _load_prompt_manifest(self) -> Optional[dict]:
manifest_path = os.path.join(self.prompts_dir, "prompt_manifest.yaml")
try:
manifest_mtime = os.path.getmtime(manifest_path)
except FileNotFoundError:
return None
if (
self._prompt_manifest_cache is not None
and self._prompt_manifest_mtime == manifest_mtime
):
return self._prompt_manifest_cache
try:
with open(manifest_path, "r", encoding="utf-8") as f:
manifest = yaml.safe_load(f) or {}
except Exception as exc:
logging.error(f"提示词清单加载失败 -> {exc}")
return None
if not isinstance(manifest, dict):
logging.error("提示词清单格式错误:顶层必须为映射")
return None
self._prompt_manifest_cache = manifest
self._prompt_manifest_mtime = manifest_mtime
return manifest
def _resolve_prompt_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"):
try:
with open(fragment_path, "r", encoding="utf-8") as f:
payload = json.load(f)
except Exception as exc:
raise ValueError(f"JSON片段解析失败: {exc}") from exc
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_prompt_from_manifest(self, scene_key: str, fallback_file: str) -> str:
manifest = self._load_prompt_manifest()
if not manifest:
return self._load_prompt(fallback_file)
scene_map = manifest.get("scenes", manifest)
fragments = scene_map.get(scene_key)
if not fragments:
logging.warning(f"提示词清单缺少场景配置 -> {scene_key}")
return self._load_prompt(fallback_file)
if not isinstance(fragments, list) or not all(isinstance(item, str) for item in fragments):
logging.warning(f"提示词清单场景配置非法 -> {scene_key}")
return self._load_prompt(fallback_file)
contents = []
for fragment in fragments:
fragment_path = self._resolve_prompt_fragment(fragment)
try:
contents.append(self._load_fragment_text(fragment_path).strip("\n"))
except FileNotFoundError:
logging.error(f"提示词片段未找到 -> {fragment_path}")
return self._load_prompt(fallback_file)
except ValueError as exc:
logging.error(f"提示词片段解析失败 -> {exc}")
return self._load_prompt(fallback_file)
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]: def _retrieve_context(self, query: str) -> Optional[str]:
logging.info("--- 开始从向量数据库检索上下文 ---") logging.info("--- 开始从向量数据库检索上下文 ---")
try: try:
@@ -619,42 +874,107 @@ class PyTreeGenerator:
return None return None
context_str = "\n\n".join(retrieved_docs) context_str = "\n\n".join(retrieved_docs)
logging.info("--- 成功检索到上下文信息 ---") logging.info("--- 成功检索到上下文信息 ---")
# 打印检索到的上下文内容
logging.info(f"📚 检索到的上下文内容:\n{context_str}")
return context_str return context_str
except Exception as e: except Exception as e:
logging.error(f"从向量数据库检索时发生错误: {e}") logging.error(f"从向量数据库检索时发生错误: {e}")
return None 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
try:
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
# 确保history目录存在
history_dir = os.path.join(self.base_dir, '..', 'history')
os.makedirs(history_dir, exist_ok=True)
history_file = os.path.join(history_dir, f"{timestamp}_plan.json")
record = {
"timestamp": datetime.datetime.now().isoformat(),
"request": prompt,
"response": result_dict
}
with open(history_file, 'w', encoding='utf-8') as f:
json.dump(record, f, ensure_ascii=False, indent=2)
logging.info(f"💾 历史记录已保存: {history_file}")
except Exception as e:
logging.error(f"保存历史记录失败: {e}")
async def generate(self, user_prompt: str) -> Dict[str, Any]: async def generate(self, user_prompt: str) -> Dict[str, Any]:
""" """
Generates a py_tree.json structure based on the user's prompt. Generates a py_tree.json structure based on the user's prompt.
""" """
logging.info(f"接收到用户请求: {user_prompt}") logging.info(f"接收到用户请求: {user_prompt}")
# 第一步:分类(简单/复杂 # 第一步:场景分类(simple/scene1/scene4
mode = "complex" scene_mode = "scene1"
try: try:
classifier_resp = self.classifier_client.chat.completions.create( classifier_resp = self.classifier_client.chat.completions.create(
model=self.classifier_model, model=self.classifier_model,
messages=[ messages=[
{"role": "system", "content": self.classifier_prompt or "你是一个分类器只输出JSON。"}, {"role": "system", "content": self.scene_classifier_prompt or "你是一个分类器只输出JSON。"},
{"role": "user", "content": user_prompt} {"role": "user", "content": user_prompt}
], ],
temperature=0.0, temperature=0.0,
response_format={"type": "json_object"}, response_format={"type": "json_object"}, # 强制JSON输出禁用思考功能
max_tokens=self.classifier_max_tokens 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_str = classifier_resp.choices[0].message.content
class_obj = json.loads(class_str) class_obj = json.loads(class_str)
if isinstance(class_obj, dict) and class_obj.get("mode") in ("simple", "complex"): if isinstance(class_obj, dict) and class_obj.get("mode") in ("simple", "scene1", "scene4"):
mode = class_obj.get("mode") scene_mode = class_obj.get("mode")
logging.info(f"分类结果: {mode}") logging.info(f"场景分类结果: {scene_mode}")
except Exception as e: except Exception as e:
logging.warning(f"分类失败,默认按复杂指令处理: {e}") logging.warning(f"场景分类失败,默认按scene1处理: {e}")
# 第二步:根据模式准备提示词与上下文(简单与复杂都执行检索增强) # 第二步:根据模式准备提示词与上下文(简单与复杂都执行检索增强)
# 基于模式选择提示词;复杂模式追加一条强制规则,避免模型误输出简单结构 # 基于场景选择提示词;非simple时追加强制规则,避免模型误输出简单结构
use_prompt = self.simple_prompt if mode == "simple" else ( if scene_mode == "simple":
(self.complex_prompt or "") + use_prompt = self.simple_prompt
elif scene_mode == "scene4":
use_prompt = self.scene4_prompt
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节点" "\n\n【强制规则】仅生成包含root的复杂行为树JSON不得输出简单模式不得包含mode字段或仅有action节点"
) )
final_user_prompt = user_prompt final_user_prompt = user_prompt
@@ -670,35 +990,98 @@ class PyTreeGenerator:
final_user_prompt += augmentation final_user_prompt += augmentation
else: else:
logging.warning("未检索到上下文或检索失败,将使用原始用户提示词。") 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): for attempt in range(3):
logging.info(f"--- 第 {attempt + 1}/3 次尝试生成Pytree ---") logging.info(f"--- 第 {attempt + 1}/3 次尝试生成Pytree ---")
try: try:
# 简单/复杂分流到不同模型与提示词 # 简单/复杂分流到不同模型与提示词
client = self.simple_llm_client if mode == "simple" else self.complex_llm_client is_simple = scene_mode == "simple"
model_name = self.simple_model if mode == "simple" else self.complex_model client = self.simple_llm_client if is_simple else self.complex_llm_client
# 根据是否捕获推理链来决定是否强制JSON响应 model_name = self.simple_model if is_simple else self.complex_model
response_kwargs = { messages = [
"model": model_name,
"messages": [
{"role": "system", "content": use_prompt}, {"role": "system", "content": use_prompt},
{"role": "user", "content": final_user_prompt} {"role": "user", "content": final_user_prompt}
], ]
"temperature": 0.1 if mode == "complex" else 0.0, # 始终强制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 not self.enable_reasoning_capture: if tool_enabled:
response_kwargs["response_format"] = {"type": "json_object"} response_kwargs["tools"] = self._get_tool_definitions()
response_kwargs["tool_choice"] = "auto"
# 基于模式设定最大输出token数直接在代码中配置 # 基于模式设定最大输出token数直接在代码中配置
response_kwargs["max_tokens"] = self.simple_max_tokens if mode == "simple" else self.complex_max_tokens response_kwargs["max_tokens"] = self.simple_max_tokens if is_simple else self.complex_max_tokens
response = client.chat.completions.create(**response_kwargs) response = client.chat.completions.create(**response_kwargs)
# 工具调用处理执行工具并回填后强制模型输出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")
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 字段 # 兼容可能存在的 reasoning_content 字段
try: try:
msg = response.choices[0].message msg = response.choices[0].message
msg_content = getattr(msg, "content", None) msg_content = getattr(msg, "content", None)
msg_reasoning = getattr(msg, "reasoning_content", None) msg_reasoning = getattr(msg, "reasoning_content", None)
remaining_tool_calls = getattr(msg, "tool_calls", None)
except Exception: except Exception:
msg = response.choices[0]["message"] if isinstance(response.choices[0], dict) else None 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_content = (msg or {}).get("content") if isinstance(msg, dict) else None
msg_reasoning = (msg or {}).get("reasoning_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 = "" combined_text = ""
if isinstance(msg_reasoning, str) and msg_reasoning.strip(): if isinstance(msg_reasoning, str) and msg_reasoning.strip():
@@ -709,7 +1092,7 @@ class PyTreeGenerator:
pytree_str = combined_text if combined_text else (msg_content or "") pytree_str = combined_text if combined_text else (msg_content or "")
raw_full_text_for_logging = pytree_str # 保存完整原文(含 <think>)以便失败时完整打印 raw_full_text_for_logging = pytree_str # 保存完整原文(含 <think>)以便失败时完整打印
# 提取 <think> 推理链内容(若存在 # 提取 <think> 推理链内容(若
reasoning_text = None reasoning_text = None
try: try:
think_match = re.search(r"<think>([\s\S]*?)</think>", pytree_str) think_match = re.search(r"<think>([\s\S]*?)</think>", pytree_str)
@@ -761,9 +1144,16 @@ class PyTreeGenerator:
continue continue
# 简单/复杂分别验证与返回 # 简单/复杂分别验证与返回
if mode == "simple": if scene_mode == "simple":
try: try:
jsonschema.validate(instance=pytree_dict, schema=self.simple_schema) jsonschema.validate(instance=pytree_dict, schema=self.simple_schema)
# 手动检查简单模式的root节点不能有children或children必须是空数组
root_node = pytree_dict.get('root', {})
if 'children' in root_node:
children = root_node.get('children', [])
if isinstance(children, list) and len(children) > 0:
logging.warning(f"❌ 简单模式验证失败: root节点不能有children但发现 {len(children)} 个子节点")
continue
logging.info("✅ 简单模式JSON Schema验证成功") logging.info("✅ 简单模式JSON Schema验证成功")
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
logging.warning(f"❌ 简单模式验证失败: {e.message}") logging.warning(f"❌ 简单模式验证失败: {e.message}")
@@ -771,16 +1161,13 @@ class PyTreeGenerator:
# 附加元信息并生成简单可视化(单动作) # 附加元信息并生成简单可视化(单动作)
plan_id = str(uuid.uuid4()) plan_id = str(uuid.uuid4())
pytree_dict['plan_id'] = plan_id pytree_dict['plan_id'] = plan_id
# 简单模式可视化:构造一个简化节点图 # 简单模式可视化:使用root节点已经是action类型
try: try:
vis_filename = "py_tree.png" vis_filename = "py_tree.png"
vis_path = os.path.join(self.vis_dir, vis_filename) vis_path = os.path.join(self.vis_dir, vis_filename)
simple_node = { # 简单模式的root节点就是action节点直接使用
"type": "action", root_node = pytree_dict.get('root', {})
"name": pytree_dict.get('action', {}).get('name', 'action'), _visualize_pytree(root_node, os.path.splitext(vis_path)[0])
"params": pytree_dict.get('action', {}).get('params', {})
}
_visualize_pytree(simple_node, os.path.splitext(vis_path)[0])
pytree_dict['visualization_url'] = f"/static/{vis_filename}" pytree_dict['visualization_url'] = f"/static/{vis_filename}"
except Exception as e: except Exception as e:
logging.warning(f"简单模式可视化失败: {e}") logging.warning(f"简单模式可视化失败: {e}")
@@ -803,48 +1190,15 @@ class PyTreeGenerator:
logging.info("未在模型输出中发现 <think> 推理链片段。若需捕获,请设置 ENABLE_REASONING_CAPTURE=true 以放宽JSON强制格式。") logging.info("未在模型输出中发现 <think> 推理链片段。若需捕获,请设置 ENABLE_REASONING_CAPTURE=true 以放宽JSON强制格式。")
except Exception as e: except Exception as e:
logging.warning(f"保存推理链Markdown失败: {e}") logging.warning(f"保存推理链Markdown失败: {e}")
# 添加 final_prompt 到返回结果
pytree_dict['final_prompt'] = final_prompt
# 保存历史记录
self._save_history(user_prompt, pytree_dict)
return pytree_dict return pytree_dict
# 复杂模式回退:若模型误返回简单结构,则自动包装为含安全监控的行为树 # 验证生成的复杂行为树
if mode == "complex" and isinstance(pytree_dict, dict) and 'root' not in pytree_dict:
try:
jsonschema.validate(instance=pytree_dict, schema=self.simple_schema)
logging.warning("⚠️ 复杂模式生成了简单结构,触发自动包装为完整行为树的回退逻辑。")
simple_action_obj = pytree_dict.get('action') or {}
action_name = simple_action_obj.get('name')
action_params = simple_action_obj.get('params') if isinstance(simple_action_obj.get('params'), dict) else {}
safety_selector = {
"type": "Selector",
"name": "SafetyMonitor",
"params": {"memory": True},
"children": [
{"type": "condition", "name": "battery_above", "params": {"threshold": 0.3}},
{"type": "condition", "name": "gps_status", "params": {"min_satellites": 8}},
{"type": "Sequence", "name": "EmergencyHandler", "children": [
{"type": "action", "name": "emergency_return", "params": {"reason": "safety_breach"}},
{"type": "action", "name": "land", "params": {"mode": "home"}}
]}
]
}
main_children = [{"type": "action", "name": action_name, "params": action_params}]
if action_name != "land":
main_children.append({"type": "action", "name": "land", "params": {"mode": "home"}})
root_parallel = {
"type": "Parallel",
"name": "MissionWithSafety",
"params": {"policy": "all_success"},
"children": [
{"type": "Sequence", "name": "MainTask", "children": main_children},
safety_selector
]
}
pytree_dict = {"root": root_parallel}
except jsonschema.ValidationError:
# 不符合简单结构,按正常复杂验证继续
pass
if _validate_pytree_with_schema(pytree_dict, self.schema): if _validate_pytree_with_schema(pytree_dict, self.schema):
logging.info("✅ 成功生成并验证了Pytree") logging.info("✅ 成功生成并验证了Pytree")
plan_id = str(uuid.uuid4()) plan_id = str(uuid.uuid4())
@@ -874,6 +1228,12 @@ class PyTreeGenerator:
logging.info("未在模型输出中发现 <think> 推理链片段。若需捕获,请设置 ENABLE_REASONING_CAPTURE=true 以放宽JSON强制格式。") logging.info("未在模型输出中发现 <think> 推理链片段。若需捕获,请设置 ENABLE_REASONING_CAPTURE=true 以放宽JSON强制格式。")
except Exception as e: except Exception as e:
logging.warning(f"保存推理链Markdown失败: {e}") logging.warning(f"保存推理链Markdown失败: {e}")
# 添加 final_prompt 到返回结果
pytree_dict['final_prompt'] = final_prompt
# 保存历史记录
self._save_history(user_prompt, pytree_dict)
return pytree_dict return pytree_dict
else: else:
# 打印未通过验证的Pytree以便排查 # 打印未通过验证的Pytree以便排查

View File

@@ -1,67 +0,0 @@
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
import json
from typing import Dict, Any
import logging
from drone_interfaces.action import ExecuteMission
from .websocket_manager import websocket_manager
class MissionActionClient(Node):
"""
Interfaces with the drone's `ExecuteMission` ROS2 Action Server.
"""
def __init__(self):
super().__init__('mission_action_client')
self._action_client = ActionClient(self, ExecuteMission, 'execute_mission')
self.get_logger().info("MissionActionClient initialized.")
def send_goal(self, py_tree: Dict[str, Any]):
"""
Sends the mission (py_tree) to the action server.
"""
if not self._action_client.server_is_ready():
self.get_logger().error("Action server not available, goal not sent.")
# Optionally, you could broadcast a status update to the frontend here
return
self.get_logger().info("Received request to send goal to drone.")
goal_msg = ExecuteMission.Goal()
goal_msg.py_tree_json = json.dumps(py_tree)
self.get_logger().info(f"Sending goal to action server...")
send_goal_future = self._action_client.send_goal_async(
goal_msg,
feedback_callback=self.feedback_callback
)
send_goal_future.add_done_callback(self.goal_response_callback)
def goal_response_callback(self, future):
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().info('Goal rejected :(')
return
self.get_logger().info('Goal accepted :)')
self._get_result_future = goal_handle.get_result_async()
self._get_result_future.add_done_callback(self.get_result_callback)
def get_result_callback(self, future):
result = future.result().result
self.get_logger().info(f'Result: {{success: {result.success}, message: {result.message}}}')
# Optionally, you can broadcast the final result via WebSocket here
def feedback_callback(self, feedback_msg):
"""
This callback is triggered by the action server.
It forwards the status to the QGC plugin via the WebSocket manager in a thread-safe manner.
"""
feedback = feedback_msg.feedback
feedback_payload = json.dumps({"node_id": feedback.node_id, "status": feedback.status})
self.get_logger().info(f"Received feedback: {feedback_payload}")
websocket_manager.broadcast(feedback_payload)
# Note: The rclpy.init() and spinning of the node will be handled in main.py

View File

@@ -0,0 +1 @@
"""工具模块包。"""

View File

@@ -0,0 +1,110 @@
from __future__ import annotations
import math
import re
from typing import Dict, Tuple
_DIRECTION_DELTAS: Dict[str, Tuple[int, int, int]] = {
"east": (1, 0, 0),
"west": (-1, 0, 0),
"north": (0, -1, 0),
"south": (0, 1, 0),
"up": (0, 0, 1),
"down": (0, 0, -1),
}
def calc_offset_enu(base: Dict[str, float], direction: str, distance: float) -> Dict[str, float]:
"""在ENU坐标系下按方向偏移固定距离。"""
if distance < 0:
raise ValueError("distance must be non-negative")
if not isinstance(base, dict):
raise ValueError("base must be an object with x/y/z")
direction_key = (direction or "").strip().lower()
if direction_key not in _DIRECTION_DELTAS:
raise ValueError(f"unsupported direction: {direction}")
try:
x = float(base["x"])
y = float(base["y"])
z = float(base["z"])
except Exception as exc:
raise ValueError("base must contain numeric x/y/z") from exc
dx, dy, dz = _DIRECTION_DELTAS[direction_key]
offset_x = x + dx * distance
offset_y = y + dy * distance
offset_z = z + dz * distance
return {"x": offset_x, "y": offset_y, "z": offset_z}
_DIRECTION_TEXT_DIAGONALS_ESU: Dict[str, Tuple[float, float]] = {
"东南": (1.0, 1.0),
"西南": (-1.0, 1.0),
"东北": (1.0, -1.0),
"西北": (-1.0, -1.0),
}
def _parse_direction_text_esu(direction_text: str) -> Tuple[float, float]:
text = (direction_text or "").strip().replace(" ", "")
if not text:
raise ValueError("direction_text must be a non-empty string")
for prefix in ("", ""):
if text.startswith(prefix):
text = text[len(prefix):]
if text in _DIRECTION_TEXT_DIAGONALS_ESU:
x, y = _DIRECTION_TEXT_DIAGONALS_ESU[text]
length = math.hypot(x, y)
return x / length, y / length
if text in ("", "西", "", ""):
if text == "":
return 1.0, 0.0
if text == "西":
return -1.0, 0.0
if text == "":
return 0.0, 1.0
return 0.0, -1.0
match = re.match(r"^(南|北)偏(东|西)(\d+(?:\.\d+)?)度?$", text)
if match:
base_dir, toward_dir, angle_str = match.groups()
angle_deg = float(angle_str)
if angle_deg < 0 or angle_deg > 90:
raise ValueError("angle must be between 0 and 90 degrees")
angle_rad = math.radians(angle_deg)
x_sign = 1.0 if toward_dir == "" else -1.0
if base_dir == "":
return math.sin(angle_rad) * x_sign, math.cos(angle_rad)
return math.sin(angle_rad) * x_sign, -math.cos(angle_rad)
raise ValueError(f"unsupported direction_text: {direction_text}")
def calc_offset_esu_direction_text(
base: Dict[str, float], direction_text: str, distance: float
) -> Dict[str, float]:
"""在ESU坐标系下按中文方位偏移固定距离。"""
if distance < 0:
raise ValueError("distance must be non-negative")
if not isinstance(base, dict):
raise ValueError("base must be an object with x/y/z")
try:
x = float(base["x"])
y = float(base["y"])
z = float(base["z"])
except Exception as exc:
raise ValueError("base must contain numeric x/y/z") from exc
dx_unit, dy_unit = _parse_direction_text_esu(direction_text)
offset_x = x + dx_unit * distance
offset_y = y + dy_unit * distance
return {"x": offset_x, "y": offset_y, "z": z}

View File

@@ -22,7 +22,8 @@ class ConnectionManager:
def broadcast(self, message: str): def broadcast(self, message: str):
""" """
Thread-safely broadcasts a message to all active WebSocket connections. Thread-safely broadcasts a message to all active WebSocket connections.
This method is designed to be called from a different thread (e.g., a ROS2 callback). This method is designed to be called from a different thread.
(Note: ROS2 callback support has been removed as the project is decoupled from ROS2)
""" """
if not self.loop: if not self.loop:
logging.error("Event loop not set in ConnectionManager. Cannot broadcast.") logging.error("Event loop not set in ConnectionManager. Cannot broadcast.")

View File

@@ -0,0 +1,248 @@
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.
.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.
.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.
.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.
.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.
.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.
.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.
.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
For more information on Execution Policies:
https://go.microsoft.com/fwlink/?LinkID=135170
#>
Param(
[Parameter(Mandatory = $false)]
[String]
$VenvDir,
[Parameter(Mandatory = $false)]
[String]
$Prompt
)
<# Function declarations --------------------------------------------------- #>
<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.
.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.
#>
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values
# The prior prompt:
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
}
# The prior PYTHONHOME:
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
}
# The prior PATH:
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
}
# Just remove the VIRTUAL_ENV altogether:
if (Test-Path -Path Env:VIRTUAL_ENV) {
Remove-Item -Path env:VIRTUAL_ENV
}
# Just remove VIRTUAL_ENV_PROMPT altogether.
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
}
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
}
# Leave deactivate function in the global namespace if requested:
if (-not $NonDestructive) {
Remove-Item -Path function:deactivate
}
}
<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.
If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.
.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
[String]
$ConfigDir
) {
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
# An empty map will be returned if no config file is found.
$pyvenvConfig = @{ }
if ($pyvenvConfigPath) {
Write-Verbose "File exists, parse `key = value` lines"
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
$pyvenvConfigContent | ForEach-Object {
$keyval = $PSItem -split "\s*=\s*", 2
if ($keyval[0] -and $keyval[1]) {
$val = $keyval[1]
# Remove extraneous quotations around a string value.
if ("'""".Contains($val.Substring(0, 1))) {
$val = $val.Substring(1, $val.Length - 2)
}
$pyvenvConfig[$keyval[0]] = $val
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
}
}
}
return $pyvenvConfig
}
<# Begin Activate script --------------------------------------------------- #>
# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
Write-Verbose "VenvDir=$VenvDir"
}
# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
$Prompt = $pyvenvCfg['prompt'];
}
else {
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
$Prompt = Split-Path -Path $venvDir -Leaf
}
}
Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"
# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir
$env:VIRTUAL_ENV_PROMPT = $Prompt
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
Write-Verbose "Setting prompt to '$Prompt'"
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT { "" }
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
function global:prompt {
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
_OLD_VIRTUAL_PROMPT
}
}
# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
Remove-Item -Path Env:PYTHONHOME
}
# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

View File

@@ -0,0 +1,76 @@
# This file must be used with "source bin/activate" *from bash*
# You cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# Call hash to forget past locations. Without forgetting
# past locations the $PATH changes we made may not be respected.
# See "man bash" for more details. hash is usually a builtin of your shell
hash -r 2> /dev/null
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
# on Windows, a path can contain colons and backslashes and has to be converted:
case "$(uname)" in
CYGWIN*|MSYS*|MINGW*)
# transform D:\path\to\venv to /d/path/to/venv on MSYS and MINGW
# and to /cygdrive/d/path/to/venv on Cygwin
VIRTUAL_ENV=$(cygpath /home/a/DronePlanning/backend_service/venv)
export VIRTUAL_ENV
;;
*)
# use the path as-is
export VIRTUAL_ENV=/home/a/DronePlanning/backend_service/venv
;;
esac
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/"bin":$PATH"
export PATH
VIRTUAL_ENV_PROMPT=venv
export VIRTUAL_ENV_PROMPT
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1="("venv") ${PS1:-}"
export PS1
fi
# Call hash to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
hash -r 2> /dev/null

View File

@@ -0,0 +1,27 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV /home/a/DronePlanning/backend_service/venv
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
setenv VIRTUAL_ENV_PROMPT venv
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
set prompt = "("venv") $prompt:q"
endif
alias pydoc python -m pydoc
rehash

View File

@@ -0,0 +1,69 @@
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
# (https://fishshell.com/). You cannot run it directly.
function deactivate -d "Exit virtual environment and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
set -e _OLD_FISH_PROMPT_OVERRIDE
# prevents error when using nested fish instances (Issue #93858)
if functions -q _old_fish_prompt
functions -e fish_prompt
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
end
set -e VIRTUAL_ENV
set -e VIRTUAL_ENV_PROMPT
if test "$argv[1]" != "nondestructive"
# Self-destruct!
functions -e deactivate
end
end
# Unset irrelevant variables.
deactivate nondestructive
set -gx VIRTUAL_ENV /home/a/DronePlanning/backend_service/venv
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
set -gx VIRTUAL_ENV_PROMPT venv
# Unset PYTHONHOME if set.
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# Save the current fish_prompt function as the function _old_fish_prompt.
functions -c fish_prompt _old_fish_prompt
# With the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command.
set -l old_status $status
# Output the venv prompt; color taken from the blue of the Python logo.
printf "%s(%s)%s " (set_color 4B8BBE) venv (set_color normal)
# Restore the return status of the previous command.
echo "exit $old_status" | .
# Output the original/"old" prompt.
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
end

View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from chromadb.cli.cli import app
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(app())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from coloredlogs.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from distro.distro import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from dotenv.__main__ import cli
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(cli())

7
backend_service/venv/bin/f2py Executable file
View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from numpy.f2py.f2py2e import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from fastapi.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from filetype.__main__ import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

7
backend_service/venv/bin/hf Executable file
View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from huggingface_hub.cli.hf import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

7
backend_service/venv/bin/httpx Executable file
View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from httpx import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from humanfriendly.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from isympy import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from jsonschema.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from markdown_it.cli.parse import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

7
backend_service/venv/bin/nltk Executable file
View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from nltk.cli import cli
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(cli())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from charset_normalizer.cli import cli_detect
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(cli_detect())

View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from numpy._configtool import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from onnxruntime.tools.onnxruntime_test import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from openai.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

7
backend_service/venv/bin/oxmsg Executable file
View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from oxmsg.cli import oxmsg
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(oxmsg())

7
backend_service/venv/bin/pip Executable file
View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

7
backend_service/venv/bin/pip3 Executable file
View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from pybase64.__main__ import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from pygments.cmdline import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from build.__main__ import entrypoint
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(entrypoint())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from rsa.cli import decrypt
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(decrypt())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from rsa.cli import encrypt
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(encrypt())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from rsa.cli import keygen
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(keygen())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from rsa.util import private_to_public
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(private_to_public())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from rsa.cli import sign
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(sign())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from rsa.cli import verify
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(verify())

View File

@@ -0,0 +1 @@
python3

View File

@@ -0,0 +1 @@
/home/huangfukk/miniconda3/bin/python3

View File

@@ -0,0 +1 @@
python3

View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from huggingface_hub.inference._mcp.cli import app
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(app())

7
backend_service/venv/bin/tqdm Executable file
View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from tqdm.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

7
backend_service/venv/bin/typer Executable file
View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from typer.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/huangfukk/DronePlanning/backend_service/venv/bin/python3
import sys
from uvicorn.main import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from watchfiles.cli import cli
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(cli())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from websockets.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/home/a/DronePlanning/backend_service/venv/bin/python3
import sys
from websocket._wsdump import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,37 @@
#!/usr/local/bin/python
# -*- coding: latin-1 -*-
"""
olefile (formerly OleFileIO_PL)
Module to read/write Microsoft OLE2 files (also called Structured Storage or
Microsoft Compound Document File Format), such as Microsoft Office 97-2003
documents, Image Composer and FlashPix files, Outlook messages, ...
This version is compatible with Python 2.6+ and 3.x
Project website: http://www.decalage.info/olefile
olefile is copyright (c) 2005-2015 Philippe Lagadec (http://www.decalage.info)
olefile is based on the OleFileIO module from the PIL library v1.1.6
See: http://www.pythonware.com/products/pil/index.htm
The Python Imaging Library (PIL) is
Copyright (c) 1997-2005 by Secret Labs AB
Copyright (c) 1995-2005 by Fredrik Lundh
See source code and LICENSE.txt for information on usage and redistribution.
"""
# The OleFileIO_PL module is for backward compatibility
try:
# first try to import olefile for Python 2.6+/3.x
from olefile.olefile import *
# import metadata not covered by *:
from olefile.olefile import __version__, __author__, __date__
except:
# if it fails, fallback to the old version olefile2 for Python 2.x:
from olefile.olefile2 import *
# import metadata not covered by *:
from olefile.olefile2 import __doc__, __version__, __author__, __date__

View File

@@ -0,0 +1,33 @@
# This is a stub package designed to roughly emulate the _yaml
# extension module, which previously existed as a standalone module
# and has been moved into the `yaml` package namespace.
# It does not perfectly mimic its old counterpart, but should get
# close enough for anyone who's relying on it even when they shouldn't.
import yaml
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
# to tread carefully when poking at it here (it may not have the attributes we expect)
if not getattr(yaml, '__with_libyaml__', False):
from sys import version_info
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
raise exc("No module named '_yaml'")
else:
from yaml._yaml import *
import warnings
warnings.warn(
'The _yaml extension module is now located at yaml._yaml'
' and its location is subject to change. To use the'
' LibYAML-based parser and emitter, import from `yaml`:'
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
DeprecationWarning
)
del warnings
# Don't `del yaml` here because yaml is actually an existing
# namespace member of _yaml.
__name__ = '_yaml'
# If the module is top-level (i.e. not a part of any specific package)
# then the attribute should be set to ''.
# https://docs.python.org/3.8/library/types.html
__package__ = ''

View File

@@ -0,0 +1,209 @@
Metadata-Version: 2.4
Name: aiofiles
Version: 25.1.0
Summary: File support for asyncio.
Project-URL: Changelog, https://github.com/Tinche/aiofiles#history
Project-URL: Bug Tracker, https://github.com/Tinche/aiofiles/issues
Project-URL: Repository, https://github.com/Tinche/aiofiles
Author-email: Tin Tvrtkovic <tinchester@gmail.com>
License: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: AsyncIO
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Python: >=3.9
Description-Content-Type: text/markdown
# aiofiles: file support for asyncio
[![PyPI](https://img.shields.io/pypi/v/aiofiles.svg)](https://pypi.python.org/pypi/aiofiles)
[![Build](https://github.com/Tinche/aiofiles/workflows/CI/badge.svg)](https://github.com/Tinche/aiofiles/actions)
[![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Tinche/882f02e3df32136c847ba90d2688f06e/raw/covbadge.json)](https://github.com/Tinche/aiofiles/actions/workflows/main.yml)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/aiofiles.svg)](https://github.com/Tinche/aiofiles)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
**aiofiles** is an Apache2 licensed library, written in Python, for handling local
disk files in asyncio applications.
Ordinary local file IO is blocking, and cannot easily and portably be made
asynchronous. This means doing file IO may interfere with asyncio applications,
which shouldn't block the executing thread. aiofiles helps with this by
introducing asynchronous versions of files that support delegating operations to
a separate thread pool.
```python
async with aiofiles.open('filename', mode='r') as f:
contents = await f.read()
print(contents)
'My file contents'
```
Asynchronous iteration is also supported.
```python
async with aiofiles.open('filename') as f:
async for line in f:
...
```
Asynchronous interface to tempfile module.
```python
async with aiofiles.tempfile.TemporaryFile('wb') as f:
await f.write(b'Hello, World!')
```
## Features
- a file API very similar to Python's standard, blocking API
- support for buffered and unbuffered binary files, and buffered text files
- support for `async`/`await` ([PEP 492](https://peps.python.org/pep-0492/)) constructs
- async interface to tempfile module
## Installation
To install aiofiles, simply:
```shell
pip install aiofiles
```
## Usage
Files are opened using the `aiofiles.open()` coroutine, which in addition to
mirroring the builtin `open` accepts optional `loop` and `executor`
arguments. If `loop` is absent, the default loop will be used, as per the
set asyncio policy. If `executor` is not specified, the default event loop
executor will be used.
In case of success, an asynchronous file object is returned with an
API identical to an ordinary file, except the following methods are coroutines
and delegate to an executor:
- `close`
- `flush`
- `isatty`
- `read`
- `readall`
- `read1`
- `readinto`
- `readline`
- `readlines`
- `seek`
- `seekable`
- `tell`
- `truncate`
- `writable`
- `write`
- `writelines`
In case of failure, one of the usual exceptions will be raised.
`aiofiles.stdin`, `aiofiles.stdout`, `aiofiles.stderr`,
`aiofiles.stdin_bytes`, `aiofiles.stdout_bytes`, and
`aiofiles.stderr_bytes` provide async access to `sys.stdin`,
`sys.stdout`, `sys.stderr`, and their corresponding `.buffer` properties.
The `aiofiles.os` module contains executor-enabled coroutine versions of
several useful `os` functions that deal with files:
- `stat`
- `statvfs`
- `sendfile`
- `rename`
- `renames`
- `replace`
- `remove`
- `unlink`
- `mkdir`
- `makedirs`
- `rmdir`
- `removedirs`
- `link`
- `symlink`
- `readlink`
- `listdir`
- `scandir`
- `access`
- `getcwd`
- `path.abspath`
- `path.exists`
- `path.isfile`
- `path.isdir`
- `path.islink`
- `path.ismount`
- `path.getsize`
- `path.getatime`
- `path.getctime`
- `path.samefile`
- `path.sameopenfile`
### Tempfile
**aiofiles.tempfile** implements the following interfaces:
- TemporaryFile
- NamedTemporaryFile
- SpooledTemporaryFile
- TemporaryDirectory
Results return wrapped with a context manager allowing use with async with and async for.
```python
async with aiofiles.tempfile.NamedTemporaryFile('wb+') as f:
await f.write(b'Line1\n Line2')
await f.seek(0)
async for line in f:
print(line)
async with aiofiles.tempfile.TemporaryDirectory() as d:
filename = os.path.join(d, "file.ext")
```
### Writing tests for aiofiles
Real file IO can be mocked by patching `aiofiles.threadpool.sync_open`
as desired. The return type also needs to be registered with the
`aiofiles.threadpool.wrap` dispatcher:
```python
aiofiles.threadpool.wrap.register(mock.MagicMock)(
lambda *args, **kwargs: aiofiles.threadpool.AsyncBufferedIOBase(*args, **kwargs)
)
async def test_stuff():
write_data = 'data'
read_file_chunks = [
b'file chunks 1',
b'file chunks 2',
b'file chunks 3',
b'',
]
file_chunks_iter = iter(read_file_chunks)
mock_file_stream = mock.MagicMock(
read=lambda *args, **kwargs: next(file_chunks_iter)
)
with mock.patch('aiofiles.threadpool.sync_open', return_value=mock_file_stream) as mock_open:
async with aiofiles.open('filename', 'w') as f:
await f.write(write_data)
assert await f.read() == b'file chunks 1'
mock_file_stream.write.assert_called_once_with(write_data)
```
### Contributing
Contributions are very welcome. Tests can be run with `tox`, please ensure
the coverage at least stays the same before you submit a pull request.

View File

@@ -0,0 +1,26 @@
aiofiles-25.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
aiofiles-25.1.0.dist-info/METADATA,sha256=a5a5kHMVigDdsBKlFINLSMPsX3Ms4Fn_zecASBdZqLU,6291
aiofiles-25.1.0.dist-info/RECORD,,
aiofiles-25.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
aiofiles-25.1.0.dist-info/licenses/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
aiofiles-25.1.0.dist-info/licenses/NOTICE,sha256=EExY0dRQvWR0wJ2LZLwBgnM6YKw9jCU-M0zegpRSD_E,55
aiofiles/__init__.py,sha256=DYqUwak6MVosBjbAsgyEnFFP-HUZCG5h7X4owoeyYHw,345
aiofiles/__pycache__/__init__.cpython-313.pyc,,
aiofiles/__pycache__/base.cpython-313.pyc,,
aiofiles/__pycache__/os.cpython-313.pyc,,
aiofiles/__pycache__/ospath.cpython-313.pyc,,
aiofiles/base.py,sha256=-fvh41PnictTZL3cg98HoN4h6jdebi5d7Mfh81zOBOc,2046
aiofiles/os.py,sha256=slJ5oUNHVW1xWVuuIWQiYjw30n3L48H7oX4CJvD_1d4,1078
aiofiles/ospath.py,sha256=c-Kqw4wMCZ-YRt8Jleb697cANwJQM9qux6lq97949C8,678
aiofiles/tempfile/__init__.py,sha256=twoC7vaQ-JjFzh2Bbd-3-o0hmExH3CYJUmQcuiVwZfg,10207
aiofiles/tempfile/__pycache__/__init__.cpython-313.pyc,,
aiofiles/tempfile/__pycache__/temptypes.cpython-313.pyc,,
aiofiles/tempfile/temptypes.py,sha256=3_hlc6l9r5wmino1fDrt4TpFlX4IKoR5IP_bBYVVuHg,2037
aiofiles/threadpool/__init__.py,sha256=-65UURmzUHsGTXUz0TARdSzyXIfkCFtbczAQLEPpEcU,3140
aiofiles/threadpool/__pycache__/__init__.cpython-313.pyc,,
aiofiles/threadpool/__pycache__/binary.cpython-313.pyc,,
aiofiles/threadpool/__pycache__/text.cpython-313.pyc,,
aiofiles/threadpool/__pycache__/utils.cpython-313.pyc,,
aiofiles/threadpool/binary.py,sha256=hp-km9VCRu0MLz_wAEUfbCz7OL7xtn9iGAawabpnp5U,2315
aiofiles/threadpool/text.py,sha256=fNmpw2PEkj0BZSldipJXAgZqVGLxALcfOMiuDQ54Eas,1223
aiofiles/threadpool/utils.py,sha256=VtIJ9KErbcIT9_Yz4V4rZgNEUjBH3cAYxzKQBMpEzik,1850

View File

@@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: hatchling 1.27.0
Root-Is-Purelib: true
Tag: py3-none-any

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,2 @@
Asyncio support for files
Copyright 2016 Tin Tvrtkovic

View File

@@ -0,0 +1,23 @@
"""Utilities for asyncio-friendly file handling."""
from . import tempfile
from .threadpool import (
open,
stderr,
stderr_bytes,
stdin,
stdin_bytes,
stdout,
stdout_bytes,
)
__all__ = [
"open",
"tempfile",
"stdin",
"stdout",
"stderr",
"stdin_bytes",
"stdout_bytes",
"stderr_bytes",
]

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