无人机自然语言控制项目
本项目构建了一个完整的无人机自然语言控制系统,集成了检索增强生成(RAG)知识库、大型语言模型(LLM)、FastAPI后端服务和ROS2通信,最终实现通过自然语言指令控制无人机执行复杂任务。
项目结构
项目被清晰地划分为几个核心模块:
.
├── backend_service/
│ ├── src/ # FastAPI应用核心代码
│ │ ├── __init__.py
│ │ ├── main.py # 应用主入口,提供Web API
│ │ ├── py_tree_generator.py # RAG与LLM集成,生成py_tree
│ │ ├── prompts/ # LLM 提示词
│ │ │ ├── system_prompt.txt # 复杂模式提示词(行为树与安全监控)
│ │ │ ├── simple_mode_prompt.txt # 简单模式提示词(单一原子动作JSON)
│ │ │ └── classifier_prompt.txt # 指令简单/复杂分类提示词
│ │ ├── ...
│ ├── generated_visualizations/ # 存放最新生成的py_tree可视化图像
│ ├── generated_reasoning_content/ # 存放最新推理链Markdown(<plan_id>.md)
│ └── requirements.txt # 后端服务的Python依赖
│
├── tools/
│ ├── map/ # 【数据源】存放原始地图文件(如.world, .json)
│ ├── knowledge_base/ # 【处理后】存放build_knowledge_base.py生成的.ndjson文件
│ ├── vector_store/ # 【数据库】存放最终的ChromaDB向量数据库
│ ├── build_knowledge_base.py # 【步骤1】用于将原始数据转换为自然语言知识
│ ├── ingest.py # 【步骤2】用于将自然语言知识摄入向量数据库
│ └── test_llama_server.py # 直接调用本地8081端口llama-server,支持 --system / --system-file
│
├── / # ROS2接口定义 (保持不变)
└── docs/
└── README.md # 本说明文件
核心配置:Orin IP 地址
重要提示: 本项目的后端服务和知识库工具需要与在NVIDIA Jetson Orin设备上运行的服务进行通信(嵌入模型和LLM推理服务),默认的IP地址为localhost,所以使用电脑本地部署的模型服务同样可以,但是需要注意指定模型的端口。
在使用前,您必须配置正确的Orin设备IP地址。您可以通过以下两种方式之一进行设置:
-
设置环境变量 (推荐): 在您的终端中设置一个名为
ORIN_IP的环境变量。export ORIN_IP="192.168.1.100" # 请替换为您的Orin设备的实际IP地址脚本会优先使用这个环境变量。
-
直接修改脚本: 如果您不想设置环境变量,可以打开
tools/ingest.py和backend_service/src/py_tree_generator.py文件,找到orin_ip = os.getenv("ORIN_IP", "...")这样的行,并将默认的IP地址修改为您的Orin设备的实际IP地址。
在继续后续步骤之前,请务必完成此项配置。
模型端口启动
本项目启动依赖于后端的模型推理服务,即ORIN_IP所指向的设备的模型服务端口,目前项目使用instruct模型与embedding模型实现流程,分别部署在8081端口与8090端口。
-
推理模型部署:
在
/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模型部署
在
/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
指令分类与分流
后端在生成任务前会先对用户指令进行“简单/复杂”分类,并分流到不同提示词与模型:
- 分类提示词:
backend_service/src/prompts/classifier_prompt.txt - 简单模式提示词:
backend_service/src/prompts/simple_mode_prompt.txt - 复杂模式提示词:
backend_service/src/prompts/system_prompt.txt
分类仅输出如下JSON之一:{"mode":"simple"} 或 {"mode":"complex"}。两种模式都会执行检索增强(RAG),将参考知识拼接到用户指令后再进行推理。
当为简单模式时,LLM仅输出:
{"mode":"simple","action":{"name":"<action>","params":{...}}}。
后端不会再自动封装为复杂行为树;将直接返回简单JSON,并附加 plan_id 与 visualization_url(单动作可视化)。
环境变量(可选)
支持为“分类/简单/复杂”三类调用分别配置模型与Base URL(未设置时回退到默认本地配置):
CLASSIFIER_MODEL,CLASSIFIER_BASE_URLSIMPLE_MODEL,SIMPLE_BASE_URLCOMPLEX_MODEL,COMPLEX_BASE_URL
通用API Key:OPENAI_API_KEY
推理链捕获相关:
ENABLE_REASONING_CAPTURE:是否允许模型返回含有 的原文以便捕获推理链;默认 true。REASONING_PREVIEW_LINES:在后端日志中打印推理链预览的行数;默认 20。
示例:
export CLASSIFIER_MODEL="qwen2.5-1.8b-instruct"
export SIMPLE_MODEL="qwen2.5-1.8b-instruct"
export COMPLEX_MODEL="qwen2.5-7b-instruct"
export CLASSIFIER_BASE_URL="http://$ORIN_IP:8081/v1"
export SIMPLE_BASE_URL="http://$ORIN_IP:8081/v1"
export COMPLEX_BASE_URL="http://$ORIN_IP:8081/v1"
export OPENAI_API_KEY="sk-no-key-required"
# 推理链捕获(可选)
export ENABLE_REASONING_CAPTURE=true # 默认已为true;如需关闭,设置为 false
export REASONING_PREVIEW_LINES=30 # 调整日志预览行数
测试简单模式
启动服务后,运行内置测试脚本:
cd tools
python test_api.py
示例输入:“简单模式,起飞” 或 “起飞到10米”。返回结果为简单JSON(无 root):包含 mode、action、plan_id、visualization_url。
直接调用 llama-server(绕过后端)
当仅需测试本地 8081 端口的推理服务(OpenAI 兼容接口)时,可使用内置脚本:
python tools/test_llama_server.py \
--system-file backend_service/src/prompts/system_prompt.txt \
--user "起飞到10米然后降落" \
--base-url "http://127.0.0.1:8081/v1" \
--verbose
说明:
- 支持
--system或--system-file自定义提示词文件;--system-file优先。 - 默认解析 OpenAI 风格返回,若包含
<think>推理内容会显示在输出中(具体取决于模型和服务配置)。
工作流程
整个系统的工作流程分为两个主要阶段:
- 知识库构建(一次性设置): 将环境信息、无人机能力等知识加工并存入向量数据库。
- 后端服务运行与交互: 启动主服务,通过API接收指令、生成并执行任务。
阶段一:环境设置与编译
此阶段为项目准备好运行环境,仅需在初次配置或依赖变更时执行。一个稳定、隔离且兼容的环境是所有后续步骤成功的基础。
1. 创建Conda环境 (关键步骤)
为了从根源上避免本地Python环境与系统ROS 2环境的库版本冲突(特别是Python版本和C++标准库),我们必须使用Conda创建一个干净、隔离且版本精确的虚拟环境。
# 1. 创建一个使用Python 3.10的新环境。
# --name backend: 指定环境名称。
# python=3.10: 指定Python版本,必须与ROS 2 Humble要求的版本一致。
# --channel conda-forge: 使用conda-forge社区源,其包通常有更好的兼容性。
# --no-default-packages: 关键!不安装Conda默认的包(如libgcc),避免与系统ROS 2的C++库冲突。
conda create --name backend --channel conda-forge --no-default-packages python=3.10
# 2. 激活新创建的环境
conda activate backend
2. 安装所有Python依赖
在激活backend环境后,使用pip一次性安装所有依赖。requirements.txt已包含运行时(如fastapi, rclpy)和编译时(如empy, catkin-pkg, lark)所需的所有库。
# 确保在项目根目录 (drone/) 下执行
pip install -r backend_service/requirements.txt
3. 编译ROS 2接口
为了让后端服务能够像导入普通Python包一样导入我们自定义的Action接口 (drone_interfaces),你需要先使用colcon对其进行编译。
# 确保在项目根目录 (drone/) 下执行
colcon build
成功后,您会看到build/, install/, log/三个新目录。这一步会将.action文件转换为Python和C++代码。
阶段二:数据处理流水线
此阶段为RAG系统准备数据,让LLM能够理解任务环境。
1. 准备原始数据
将你的原始数据文件(例如,.world, .json 文件等)放入 tools/map/ 目录中。
2. 数据预处理
运行脚本将原始数据“翻译”成自然语言知识。
# 确保在项目根目录 (drone/) 下,并已激活backend环境
cd tools
python build_knowledge_base.py
该脚本会扫描 tools/map/ 目录,并在 tools/knowledge_base/ 目录下生成对应的 _knowledge.ndjson 文件。
3. 数据入库(Ingestion)
运行脚本将处理好的知识加载到向量数据库中。
# 仍在tools/目录下执行
python ingest.py
该脚本会自动扫描 tools/knowledge_base/ 目录,并将数据存入 tools/vector_store/ 目录中。
阶段三:服务启动与测试
完成前两个阶段后,即可启动并测试后端服务。
1. 启动所有服务(推荐方式:一键启动脚本)
我们提供了一个一键启动脚本 start_all.sh,可以自动启动所有必需的服务:
# 1. 切换到项目根目录
cd /path/to/your/drone
# 2. 使用一键启动脚本(推荐)
./start_all.sh start
# 或者直接运行(start是默认命令)
./start_all.sh
脚本功能:
- 自动启动推理模型服务(llama-server,端口8081)
- 自动启动Embedding模型服务(llama-server,端口8090)
- 自动启动FastAPI后端服务(端口8000)
- 自动检查端口占用、模型文件、环境配置等
- 自动等待服务就绪
- 统一管理日志文件(保存在
logs/目录)
环境变量配置(可选):
在运行脚本前,可以通过环境变量自定义配置:
# 设置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
脚本命令:
./start_all.sh start # 启动所有服务(默认)
./start_all.sh stop # 停止所有服务
./start_all.sh restart # 重启所有服务
./start_all.sh status # 查看服务状态
日志查看:
所有服务的日志都保存在 logs/ 目录下:
# 查看所有日志
tail -f logs/*.log
# 查看特定服务日志
tail -f logs/inference_model.log # 推理模型
tail -f logs/embedding_model.log # Embedding模型
tail -f logs/fastapi.log # FastAPI服务
2. 手动启动服务(备选方式)
如果您需要手动控制每个服务的启动,可以按照以下步骤操作:
启动推理模型服务:
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模型服务:
在另一个终端中:
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后端服务:
在第三个终端中:
# 1. 切换到项目根目录
cd /path/to/your/drone
# 2. 激活ROS 2编译环境
# 作用:将我们编译好的`drone_interfaces`包的路径告知系统,否则Python会报`ModuleNotFoundError`。
# 注意:此命令必须在每次打开新终端时执行一次。
source install/setup.bash
# 3. 激活Conda Python环境
conda activate backend
# 4. 启动FastAPI服务
cd backend_service/
uvicorn src.main:app --host 0.0.0.0 --port 8000
当您看到日志中出现 Uvicorn running on http://0.0.0.0:8000 时,表示服务已成功启动。
2. 运行API接口测试
我们提供了一个脚本来验证核心的“任务生成”功能。
打开一个新的终端,并执行以下命令:
# 1. 切换到项目根目录
cd /path/to/your/drone
# 2. 激活Conda环境
conda activate backend
# 3. 运行测试脚本
cd tools/
python test_api.py
如果一切正常,您将在终端看到一系列 PASS 信息,以及从服务器返回的Pytree JSON。
3. API接口使用说明
故障排除 / 常见问题 (FAQ)
以下是在配置和运行此项目时可能遇到的一些常见问题及其解决方案。
Q1: 启动服务时报错 ModuleNotFoundError: No module named 'drone_interfaces'
- 原因: 您当前的终端环境没有加载ROS 2工作空间的路径。仅仅激活Conda环境是不够的。
- 解决方案: 严格遵循“启动后端服务”章节的说明,在激活Conda环境之前,必须先运行
source install/setup.bash命令。
Q2: colcon build 编译失败,提示 ModuleNotFoundError: No module named 'em', 'catkin_pkg', 或 'lark'
- 原因: 您的Python环境中缺少ROS 2编译代码时所必需的依赖包。
- 解决方案: 我们已将所有已知的编译时依赖(
empy,catkin-pkg,lark等)添加到了requirements.txt中。请确保您已激活正确的Conda环境,然后运行pip install -r backend_service/requirements.txt来安装它们。
Q3: 启动服务时报错 ImportError: ... GLIBCXX_... not found 或 ModuleNotFoundError: No module named 'rclpy._rclpy_pybind11'
- 原因: 您的Conda环境与系统ROS 2环境存在核心库冲突。最常见的原因是Python版本不匹配(例如,Conda是Python 3.11而ROS 2 Humble需要3.10),或者Conda自带的C++库与系统库冲突。
- 解决方案: 这是最棘手的环境问题。最可靠的解决方法是彻底删除当前的Conda环境 (
conda env remove --name backend),然后严格按照本文档「环境设置」章节的说明,用正确的命令 (conda create --name backend --channel conda-forge --no-default-packages python=3.10) 重建一个干净、兼容的环境。
Q4: 服务启动时,日志显示正在从网络上下载模型(例如 all-MiniLM-L6-v2)
- 原因: 后端服务在连接向量数据库时,没有正确指定使用远程嵌入模型,导致ChromaDB退回到默认的、需要下载模型的本地嵌入函数。
- 解决方案: 此问题在当前代码中已被修复。
backend_service/src/py_tree_generator.py现在会正确地将远程嵌入函数实例传递给ChromaDB。如果您在自己的代码中遇到此问题,请检查您的get_collection调用。
Q5: 服务启动时,日志停在 waiting for action server...,无法访问API
- 原因: 代码中存在阻塞式的
wait_for_server()调用,它会一直等待直到无人机端的Action服务器上线,从而卡住了Web服务的启动流程。 - 解决方案: 此问题在当前代码中已被修复。
backend_service/src/ros2_client.py现在使用非阻塞的方式初始化,并在发送任务时检查服务器是否可用。
A. 生成任务计划
接收自然语言指令,返回生成的行为树(py_tree)JSON。
- Endpoint:
POST /generate_plan - Request Body:
{ "user_prompt": "无人机起飞到10米,然后前往机库,最后降落。" } - Success Response(复杂模式):
{ "root": { ... }, "plan_id": "some-unique-id", "visualization_url": "/static/py_tree.png" } - Success Response(简单模式):
{ "mode": "simple", "action": { "name": "takeoff", "params": { "altitude": 10.0 } }, "plan_id": "some-unique-id", "visualization_url": "/static/py_tree.png" }
B. 查看任务可视化
获取最新生成的行为树的可视化图像。
- Endpoint:
GET /static/py_tree.png - Usage: 在浏览器中直接打开
http://<服务器IP>:8000/static/py_tree.png即可查看。每次成功调用/generate_plan后,该图像都会被更新。
C. 执行任务
接收一个py_tree JSON,下发给无人机执行(当前为模拟执行)。
- Endpoint:
POST /execute_mission - Request Body: (使用
/generate_plan返回的root对象){ "py_tree": { "root": { ... } } } - Response:
{ "status": "execution_started" }
D. 接收实时状态
通过WebSocket连接,实时接收无人机在执行任务时的状态反馈。
- Endpoint:
WS /ws/status - Usage: 使用任意WebSocket客户端连接到
ws://<服务器IP>:8000/ws/status。当任务执行时,服务器会主动推送JSON消息,例如:{"node_id": "takeoff_node_1", "status": 0} // 0: RUNNING {"node_id": "takeoff_node_1", "status": 1} // 1: SUCCESS