AI重构
BIN
tools/__pycache__/ground_station_client.cpython-313.pyc
Normal file
BIN
tools/__pycache__/test_api.cpython-313.pyc
Normal file
BIN
tools/__pycache__/test_llama_server.cpython-313.pyc
Normal file
@@ -2165,3 +2165,7 @@
|
||||
[2025-12-06 13:42:34] ✅ Test completed successfully
|
||||
[2025-12-06 13:42:34] ================================================================================
|
||||
[2025-12-06 13:42:34]
|
||||
[2026-02-03 22:26:39] ================================================================================
|
||||
[2026-02-03 22:26:39] --- API Test: Generate Plan ---
|
||||
[2026-02-03 22:26:39] URL: http://127.0.0.1:8000/generate_plan
|
||||
[2026-02-03 22:26:39] Sending Prompt: "无人机起飞到80米高度后,先移动至搜索区,搜索并锁定任一红色车辆,跟踪接近距离目标车辆10m后进行拍照,完成拍照后返回。"
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
from pathlib import Path
|
||||
import logging
|
||||
import os
|
||||
|
||||
# --- 配置日志 ---
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
def process_osm_json(input_path: Path) -> list[str]:
|
||||
"""
|
||||
处理OpenStreetMap的JSON文件,返回描述性句子列表。
|
||||
"""
|
||||
logging.info(f"正在以OSM JSON格式处理文件: {input_path.name}")
|
||||
descriptions = []
|
||||
try:
|
||||
with open(input_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logging.error(f"读取或解析 {input_path.name} 时出错: {e}")
|
||||
return []
|
||||
|
||||
elements = data.get('elements', [])
|
||||
if not elements:
|
||||
return []
|
||||
|
||||
nodes_map = {node['id']: node for node in elements if node.get('type') == 'node'}
|
||||
ways = [elem for elem in elements if elem.get('type') == 'way']
|
||||
|
||||
for way in ways:
|
||||
tags = way.get('tags', {})
|
||||
if 'name' not in tags:
|
||||
continue
|
||||
|
||||
way_name = tags.get('name')
|
||||
way_nodes_ids = way.get('nodes', [])
|
||||
if not way_nodes_ids:
|
||||
continue
|
||||
|
||||
total_lat, total_lon, node_count = 0, 0, 0
|
||||
for node_id in way_nodes_ids:
|
||||
node_info = nodes_map.get(node_id)
|
||||
if node_info:
|
||||
total_lat += node_info.get('lat', 0)
|
||||
total_lon += node_info.get('lon', 0)
|
||||
node_count += 1
|
||||
|
||||
if node_count == 0:
|
||||
continue
|
||||
|
||||
center_lat = total_lat / node_count
|
||||
center_lon = total_lon / node_count
|
||||
|
||||
sentence = f"在地图上有一个名为 '{way_name}' 的地点或区域"
|
||||
other_tags = {k: v for k, v in tags.items() if k != 'name'}
|
||||
if other_tags:
|
||||
tag_descs = [f"{key}是'{value}'" for key, value in other_tags.items()]
|
||||
sentence += f",它的{ '、'.join(tag_descs) }"
|
||||
sentence += f",其中心位置坐标大约在 ({center_lat:.6f}, {center_lon:.6f})。"
|
||||
descriptions.append(sentence)
|
||||
|
||||
logging.info(f"从 {input_path.name} 提取了 {len(descriptions)} 条位置描述。")
|
||||
return descriptions
|
||||
|
||||
|
||||
def process_gazebo_world(input_path: Path) -> list[str]:
|
||||
"""
|
||||
处理Gazebo的.world文件,返回描述性句子列表。
|
||||
"""
|
||||
logging.info(f"正在以Gazebo World格式处理文件: {input_path.name}")
|
||||
descriptions = []
|
||||
try:
|
||||
tree = ET.parse(input_path)
|
||||
root = tree.getroot()
|
||||
except ET.ParseError as e:
|
||||
logging.error(f"解析XML文件 {input_path.name} 失败: {e}")
|
||||
return []
|
||||
|
||||
models = root.findall('.//model')
|
||||
for model in models:
|
||||
model_name = model.get('name')
|
||||
pose_element = model.find('pose')
|
||||
|
||||
if model_name and pose_element is not None and pose_element.text:
|
||||
try:
|
||||
pose_values = [float(p) for p in pose_element.text.strip().split()]
|
||||
sentence = (
|
||||
f"仿真环境中有一个名为 '{model_name}' 的物体,"
|
||||
f"其位置和姿态(x, y, z, roll, pitch, yaw)为: {pose_values}。"
|
||||
)
|
||||
descriptions.append(sentence)
|
||||
except (ValueError, IndexError):
|
||||
logging.warning(f"跳过模型 '{model_name}',因其pose格式不正确。")
|
||||
|
||||
logging.info(f"从 {input_path.name} 提取了 {len(descriptions)} 个物体信息。")
|
||||
return descriptions
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
主函数,扫描源数据目录,为每个文件生成独立的NDJSON知识库。
|
||||
"""
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
# 输入源: tools/map/
|
||||
source_data_dir = script_dir / 'map'
|
||||
# 输出目录: tools/knowledge_base/
|
||||
output_knowledge_base_dir = script_dir / 'knowledge_base'
|
||||
|
||||
if not source_data_dir.exists():
|
||||
logging.error(f"源数据目录不存在: {source_data_dir}")
|
||||
return
|
||||
|
||||
# 确保输出目录存在
|
||||
output_knowledge_base_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
total_files_processed = 0
|
||||
logging.info(f"--- 开始扫描源数据目录: {source_data_dir} ---")
|
||||
|
||||
for file_path in source_data_dir.iterdir():
|
||||
if not file_path.is_file():
|
||||
continue
|
||||
|
||||
descriptions = []
|
||||
if file_path.suffix == '.json':
|
||||
descriptions = process_osm_json(file_path)
|
||||
elif file_path.suffix == '.world':
|
||||
descriptions = process_gazebo_world(file_path)
|
||||
else:
|
||||
logging.warning(f"跳过不支持的文件类型: {file_path.name}")
|
||||
continue
|
||||
|
||||
if not descriptions:
|
||||
logging.warning(f"未能从 {file_path.name} 提取有效信息,跳过生成文件。")
|
||||
continue
|
||||
|
||||
output_filename = file_path.stem + '_knowledge.ndjson'
|
||||
output_path = output_knowledge_base_dir / output_filename
|
||||
|
||||
try:
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
for sentence in descriptions:
|
||||
json_record = {"text": sentence}
|
||||
f.write(json.dumps(json_record, ensure_ascii=False) + '\n')
|
||||
logging.info(f"成功为 '{file_path.name}' 生成知识库文件: {output_path.name}")
|
||||
total_files_processed += 1
|
||||
except IOError as e:
|
||||
logging.error(f"写入输出文件 '{output_path.name}' 失败: {e}")
|
||||
|
||||
logging.info("--- 数据处理完成 ---")
|
||||
if total_files_processed > 0:
|
||||
logging.info(f"共为 {total_files_processed} 个源文件生成了知识库。")
|
||||
else:
|
||||
logging.warning("未生成任何知识库文件。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
194
tools/ingest.py
@@ -1,194 +0,0 @@
|
||||
# 该代码用于将本地知识库中的文档导入到ChromaDB中,并使用远程嵌入模型进行向量化
|
||||
import os
|
||||
from pathlib import Path
|
||||
import chromadb
|
||||
# from chromadb.utils import embedding_functions - 不再需要
|
||||
from chromadb.api.types import Documents, EmbeddingFunction, Embeddings, Embeddable
|
||||
from unstructured.partition.auto import partition
|
||||
from rich.progress import track
|
||||
import logging
|
||||
import requests # 导入requests
|
||||
import json # 导入json模块
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
# --- 配置 ---
|
||||
# 获取脚本所在目录,确保路径的正确性
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
KNOWLEDGE_BASE_DIR = SCRIPT_DIR / "knowledge_base"
|
||||
VECTOR_STORE_DIR = SCRIPT_DIR / "vector_store"
|
||||
COLLECTION_NAME = "drone_docs"
|
||||
# EMBEDDING_MODEL_NAME = "bge-small-zh-v1.5" # 不再需要,模型名在函数内部处理
|
||||
|
||||
# --- 自定义远程嵌入函数 ---
|
||||
class RemoteEmbeddingFunction(EmbeddingFunction[Embeddable]):
|
||||
"""
|
||||
一个使用远程、兼容OpenAI API的嵌入服务的嵌入函数。
|
||||
"""
|
||||
def __init__(self, api_url: str):
|
||||
self._api_url = api_url
|
||||
logging.info(f"自定义嵌入函数已初始化,将连接到: {self._api_url}")
|
||||
|
||||
def __call__(self, input: Embeddable) -> Embeddings:
|
||||
"""
|
||||
对输入的文档进行嵌入。
|
||||
"""
|
||||
# 我们的服务只能处理文本,所以检查输入是否为字符串列表
|
||||
if not isinstance(input, list) or not all(isinstance(doc, str) for doc in input):
|
||||
logging.error("此嵌入函数仅支持字符串列表(文档)作为输入。")
|
||||
return []
|
||||
|
||||
try:
|
||||
# 移除 "model" 参数,因为embedding服务可能不需要它
|
||||
response = requests.post(
|
||||
self._api_url,
|
||||
json={"input": input},
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
response.raise_for_status() # 如果请求失败则抛出HTTPError
|
||||
|
||||
# 按照OpenAI API的格式解析返回的嵌入向量
|
||||
data = response.json().get("data", [])
|
||||
if not data:
|
||||
raise ValueError("API响应中没有找到'data'字段或'data'为空")
|
||||
|
||||
embeddings = [item['embedding'] for item in data]
|
||||
return embeddings
|
||||
|
||||
except requests.RequestException as e:
|
||||
logging.error(f"调用嵌入API失败: {e}")
|
||||
# 返回一个空列表或根据需要处理错误
|
||||
return []
|
||||
except (ValueError, KeyError) as e:
|
||||
logging.error(f"解析API响应失败: {e}")
|
||||
logging.error(f"收到的响应内容: {response.text}")
|
||||
return []
|
||||
|
||||
|
||||
def get_documents(directory: Path):
|
||||
"""从知识库目录加载所有文档并进行切分"""
|
||||
documents = []
|
||||
logging.info(f"从 '{directory}' 加载文档...")
|
||||
for file_path in directory.rglob("*"):
|
||||
if file_path.is_file() and not file_path.name.startswith('.'):
|
||||
try:
|
||||
# 对简单文本文件直接读取
|
||||
if file_path.suffix in ['.txt', '.md']:
|
||||
text = file_path.read_text(encoding='utf-8')
|
||||
documents.append({
|
||||
"text": text,
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
})
|
||||
logging.info(f"成功处理文本文件: {file_path.name}")
|
||||
# 特别处理常规的JSON文件
|
||||
elif file_path.suffix == '.json':
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
if 'elements' in data and isinstance(data['elements'], list):
|
||||
for element in data['elements']:
|
||||
documents.append({
|
||||
"text": json.dumps(element, ensure_ascii=False),
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
})
|
||||
logging.info(f"成功处理JSON文件: {file_path.name}, 提取了 {len(data['elements'])} 个元素。")
|
||||
else:
|
||||
documents.append({
|
||||
"text": json.dumps(data, ensure_ascii=False),
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
})
|
||||
logging.info(f"成功处理JSON文件: {file_path.name} (作为单个文档)")
|
||||
# 新增:专门处理我们生成的 NDJSON 文件
|
||||
elif file_path.suffix == '.ndjson':
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
count = 0
|
||||
for line in f:
|
||||
try:
|
||||
record = json.loads(line)
|
||||
if 'text' in record and isinstance(record['text'], str):
|
||||
documents.append({
|
||||
"text": record['text'],
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
})
|
||||
count += 1
|
||||
except json.JSONDecodeError:
|
||||
logging.warning(f"跳过无效的JSON行: {line.strip()}")
|
||||
if count > 0:
|
||||
logging.info(f"成功处理NDJSON文件: {file_path.name}, 提取了 {count} 个文档。")
|
||||
# 对其他所有文件类型,使用unstructured
|
||||
else:
|
||||
elements = partition(filename=str(file_path))
|
||||
for element in elements:
|
||||
documents.append({
|
||||
"text": element.text,
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
})
|
||||
logging.info(f"成功处理文件: {file_path.name} (使用unstructured)")
|
||||
except Exception as e:
|
||||
logging.error(f"处理文件 {file_path.name} 失败: {e}")
|
||||
return documents
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数,执行文档入库流程"""
|
||||
if not KNOWLEDGE_BASE_DIR.exists():
|
||||
KNOWLEDGE_BASE_DIR.mkdir(parents=True)
|
||||
logging.warning(f"知识库目录不存在,已自动创建: {KNOWLEDGE_BASE_DIR}")
|
||||
logging.warning("请向该目录中添加您的知识文件(如 .txt, .pdf, .md)。")
|
||||
return
|
||||
|
||||
# 1. 加载并切分文档
|
||||
docs_to_ingest = get_documents(KNOWLEDGE_BASE_DIR)
|
||||
if not docs_to_ingest:
|
||||
logging.warning("在知识库中未找到可处理的文档。")
|
||||
return
|
||||
|
||||
# 2. 初始化ChromaDB客户端和远程嵌入函数
|
||||
orin_ip = os.getenv("ORIN_IP", "localhost")
|
||||
embedding_api_url = f"http://{orin_ip}:8090/v1/embeddings"
|
||||
|
||||
logging.info(f"正在初始化远程嵌入函数,目标服务地址: {embedding_api_url}")
|
||||
embedding_func = RemoteEmbeddingFunction(api_url=embedding_api_url)
|
||||
|
||||
client = chromadb.PersistentClient(path=str(VECTOR_STORE_DIR))
|
||||
|
||||
# 3. 创建或获取集合
|
||||
logging.info(f"正在访问ChromaDB集合: {COLLECTION_NAME}")
|
||||
collection = client.get_or_create_collection(
|
||||
name=COLLECTION_NAME,
|
||||
embedding_function=embedding_func
|
||||
)
|
||||
|
||||
# 4. 将文档向量化并存入数据库
|
||||
logging.info(f"开始将 {len(docs_to_ingest)} 个文档块入库...")
|
||||
|
||||
# 为了避免重复添加,可以先检查
|
||||
# (这里为了简单,我们每次都重新添加,生产环境需要更复杂的逻辑)
|
||||
|
||||
doc_texts = [doc['text'] for doc in docs_to_ingest]
|
||||
metadatas = [doc['metadata'] for doc in docs_to_ingest]
|
||||
ids = [f"doc_{KNOWLEDGE_BASE_DIR.name}_{i}" for i in range(len(doc_texts))]
|
||||
|
||||
try:
|
||||
# ChromaDB的add方法会自动处理嵌入
|
||||
collection.add(
|
||||
documents=doc_texts,
|
||||
metadatas=metadatas,
|
||||
ids=ids
|
||||
)
|
||||
logging.info("所有文档块已成功入库!")
|
||||
except Exception as e:
|
||||
logging.error(f"向ChromaDB添加文档时出错: {e}")
|
||||
|
||||
|
||||
# 验证一下
|
||||
count = collection.count()
|
||||
logging.info(f"数据库中现在有 {count} 个条目。")
|
||||
|
||||
print("\n✅ 数据入库完成!")
|
||||
print(f"知识库位于: {KNOWLEDGE_BASE_DIR}")
|
||||
print(f"向量数据库位于: {VECTOR_STORE_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,39 +1,62 @@
|
||||
# RAG & Map Tools
|
||||
# RAG 工具说明
|
||||
|
||||
该目录包含了地图构建、知识库生成和向量数据库管理的相关工具。
|
||||
该目录负责知识构建与向量入库。
|
||||
|
||||
## 目录结构
|
||||
|
||||
- **knowledge_base/**: 存放源文档数据。
|
||||
- 支持格式: `.txt`, `.md`, `.pdf`
|
||||
- 生成格式: `.json`, `.ndjson` (由 `build_knowledge_base.py` 生成)
|
||||
|
||||
- **map/**: 存放地图原始数据。
|
||||
- `.osm` (OpenStreetMap 数据)
|
||||
- `.world` (Gazebo 仿真环境数据)
|
||||
|
||||
- **vector_store/**: ChromaDB 向量数据库的持久化存储目录。
|
||||
|
||||
## 脚本说明
|
||||
|
||||
### 1. `build_knowledge_base.py`
|
||||
**功能**: 处理 `map/` 目录下的地图文件,提取地理信息和语义描述,生成知识库文件到 `knowledge_base/` 目录。
|
||||
**使用方法**:
|
||||
```bash
|
||||
python build_knowledge_base.py
|
||||
```text
|
||||
tools/rag/
|
||||
├── map/ # 原始地图数据
|
||||
├── knowledge_base/
|
||||
│ ├── location/ # 地点知识(可选分目录)
|
||||
│ ├── pattern/ # 模式知识(任务模板)
|
||||
│ ├── rules/ # 规则知识
|
||||
│ └── *.ndjson
|
||||
├── vector_store/ # Chroma 持久化目录
|
||||
├── build_knowledge_base.py # 从 map 构建 ndjson
|
||||
└── ingest.py # 入库到 Chroma(多集合 + 兼容集合)
|
||||
```
|
||||
|
||||
### 2. `ingest.py`
|
||||
**功能**: 读取 `knowledge_base/` 中的所有文档,调用嵌入模型(Embedding Model)将其向量化,并存入 `vector_store/` 中的 ChromaDB 数据库。
|
||||
**使用方法**:
|
||||
## 运行前准备
|
||||
|
||||
```bash
|
||||
python ingest.py
|
||||
cd /home/huangfukk/DronePlanning
|
||||
source backend_service/venv/bin/activate
|
||||
export ORIN_IP="localhost"
|
||||
```
|
||||
**依赖**: 需要确保后端嵌入服务(如 `llama-server`)已启动,或者配置正确的 `ORIN_IP` 环境变量。
|
||||
|
||||
## 工作流
|
||||
1. 将地图文件放入 `map/`。
|
||||
2. 运行 `build_knowledge_base.py` 生成文本描述。
|
||||
3. 将其他补充文档放入 `knowledge_base/`。
|
||||
4. 运行 `ingest.py` 构建向量索引。
|
||||
并确保 embedding 服务可用(默认 `8090`):
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8090/v1/embeddings \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"input":["test"]}'
|
||||
```
|
||||
|
||||
## 完整流程(可直接执行)
|
||||
|
||||
```bash
|
||||
cd /home/huangfukk/DronePlanning
|
||||
source backend_service/venv/bin/activate
|
||||
|
||||
# 1) 由 map 构建知识文本
|
||||
python tools/rag/build_knowledge_base.py
|
||||
|
||||
# 2) 入库
|
||||
python tools/rag/ingest.py
|
||||
```
|
||||
|
||||
## 入库结果
|
||||
|
||||
`ingest.py` 会同时写入:
|
||||
|
||||
- 兼容集合:`drone_docs`
|
||||
- 新集合:`location_kb`、`pattern_kb`、`rules_kb`
|
||||
|
||||
并在 metadata 中写入 `kb_type`,支持检索时按知识域筛选。
|
||||
|
||||
## 常见问题
|
||||
|
||||
- **Chroma 初始化异常**:先确认使用的是项目 venv,再检查 `tools/rag/vector_store/` 是否损坏(备份后重建)。
|
||||
- **连接 embedding 失败**:检查 `ORIN_IP`、端口 8090、以及模型服务是否已启动。
|
||||
|
||||
|
||||
BIN
tools/rag/__pycache__/build_knowledge_base.cpython-313.pyc
Normal file
BIN
tools/rag/__pycache__/ingest.cpython-313.pyc
Normal file
@@ -5,7 +5,6 @@ import chromadb
|
||||
# from chromadb.utils import embedding_functions - 不再需要
|
||||
from chromadb.api.types import Documents, EmbeddingFunction, Embeddings, Embeddable
|
||||
from unstructured.partition.auto import partition
|
||||
from rich.progress import track
|
||||
import logging
|
||||
import requests # 导入requests
|
||||
import json # 导入json模块
|
||||
@@ -19,6 +18,11 @@ SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
KNOWLEDGE_BASE_DIR = SCRIPT_DIR / "knowledge_base"
|
||||
VECTOR_STORE_DIR = SCRIPT_DIR / "vector_store"
|
||||
COLLECTION_NAME = "drone_docs"
|
||||
COLLECTIONS_BY_KB_TYPE = {
|
||||
"location": "location_kb",
|
||||
"pattern": "pattern_kb",
|
||||
"rules": "rules_kb",
|
||||
}
|
||||
# EMBEDDING_MODEL_NAME = "bge-small-zh-v1.5" # 不再需要,模型名在函数内部处理
|
||||
|
||||
# --- 自定义远程嵌入函数 ---
|
||||
@@ -66,6 +70,18 @@ class RemoteEmbeddingFunction(EmbeddingFunction[Embeddable]):
|
||||
return []
|
||||
|
||||
|
||||
def _infer_kb_type(file_path: Path, base_dir: Path) -> str:
|
||||
try:
|
||||
rel = file_path.relative_to(base_dir)
|
||||
except ValueError:
|
||||
return "location"
|
||||
if len(rel.parts) > 1:
|
||||
first = rel.parts[0].lower()
|
||||
if first in COLLECTIONS_BY_KB_TYPE:
|
||||
return first
|
||||
return "location"
|
||||
|
||||
|
||||
def get_documents(directory: Path):
|
||||
"""从知识库目录加载所有文档并进行切分"""
|
||||
documents = []
|
||||
@@ -74,11 +90,12 @@ def get_documents(directory: Path):
|
||||
if file_path.is_file() and not file_path.name.startswith('.'):
|
||||
try:
|
||||
# 对简单文本文件直接读取
|
||||
kb_type = _infer_kb_type(file_path, directory)
|
||||
if file_path.suffix in ['.txt', '.md']:
|
||||
text = file_path.read_text(encoding='utf-8')
|
||||
documents.append({
|
||||
"text": text,
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
"metadata": {"source": str(file_path.name), "kb_type": kb_type}
|
||||
})
|
||||
logging.info(f"成功处理文本文件: {file_path.name}")
|
||||
# 特别处理常规的JSON文件
|
||||
@@ -89,13 +106,13 @@ def get_documents(directory: Path):
|
||||
for element in data['elements']:
|
||||
documents.append({
|
||||
"text": json.dumps(element, ensure_ascii=False),
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
"metadata": {"source": str(file_path.name), "kb_type": kb_type}
|
||||
})
|
||||
logging.info(f"成功处理JSON文件: {file_path.name}, 提取了 {len(data['elements'])} 个元素。")
|
||||
else:
|
||||
documents.append({
|
||||
"text": json.dumps(data, ensure_ascii=False),
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
"metadata": {"source": str(file_path.name), "kb_type": kb_type}
|
||||
})
|
||||
logging.info(f"成功处理JSON文件: {file_path.name} (作为单个文档)")
|
||||
# 新增:专门处理我们生成的 NDJSON 文件
|
||||
@@ -115,7 +132,7 @@ def get_documents(directory: Path):
|
||||
text = json.dumps(record, ensure_ascii=False)
|
||||
documents.append({
|
||||
"text": text,
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
"metadata": {"source": str(file_path.name), "kb_type": kb_type}
|
||||
})
|
||||
count += 1
|
||||
except json.JSONDecodeError:
|
||||
@@ -128,7 +145,7 @@ def get_documents(directory: Path):
|
||||
for element in elements:
|
||||
documents.append({
|
||||
"text": element.text,
|
||||
"metadata": {"source": str(file_path.name)}
|
||||
"metadata": {"source": str(file_path.name), "kb_type": kb_type}
|
||||
})
|
||||
logging.info(f"成功处理文件: {file_path.name} (使用unstructured)")
|
||||
except Exception as e:
|
||||
@@ -159,38 +176,48 @@ def main():
|
||||
|
||||
client = chromadb.PersistentClient(path=str(VECTOR_STORE_DIR))
|
||||
|
||||
# 3. 创建或获取集合
|
||||
logging.info(f"正在访问ChromaDB集合: {COLLECTION_NAME}")
|
||||
collection = client.get_or_create_collection(
|
||||
name=COLLECTION_NAME,
|
||||
embedding_function=embedding_func
|
||||
)
|
||||
# 3. 创建或获取集合(兼容旧集合 + 新多知识库集合)
|
||||
collections = {
|
||||
"fallback": client.get_or_create_collection(name=COLLECTION_NAME, embedding_function=embedding_func)
|
||||
}
|
||||
for kb_type, coll_name in COLLECTIONS_BY_KB_TYPE.items():
|
||||
collections[kb_type] = client.get_or_create_collection(name=coll_name, embedding_function=embedding_func)
|
||||
|
||||
# 4. 将文档向量化并存入数据库
|
||||
logging.info(f"开始将 {len(docs_to_ingest)} 个文档块入库...")
|
||||
|
||||
# 为了避免重复添加,可以先检查
|
||||
# (这里为了简单,我们每次都重新添加,生产环境需要更复杂的逻辑)
|
||||
|
||||
doc_texts = [doc['text'] for doc in docs_to_ingest]
|
||||
metadatas = [doc['metadata'] for doc in docs_to_ingest]
|
||||
ids = [f"doc_{KNOWLEDGE_BASE_DIR.name}_{i}" for i in range(len(doc_texts))]
|
||||
grouped_docs = {}
|
||||
for doc in docs_to_ingest:
|
||||
kb_type = doc["metadata"].get("kb_type", "location")
|
||||
grouped_docs.setdefault(kb_type, []).append(doc)
|
||||
|
||||
# 先入旧集合,保持兼容
|
||||
all_texts = [doc['text'] for doc in docs_to_ingest]
|
||||
all_metadatas = [doc['metadata'] for doc in docs_to_ingest]
|
||||
all_ids = [f"doc_{KNOWLEDGE_BASE_DIR.name}_{i}" for i in range(len(all_texts))]
|
||||
try:
|
||||
# ChromaDB的add方法会自动处理嵌入
|
||||
collection.add(
|
||||
documents=doc_texts,
|
||||
metadatas=metadatas,
|
||||
ids=ids
|
||||
)
|
||||
logging.info("所有文档块已成功入库!")
|
||||
collections["fallback"].add(documents=all_texts, metadatas=all_metadatas, ids=all_ids)
|
||||
logging.info("兼容集合 drone_docs 入库完成。")
|
||||
except Exception as e:
|
||||
logging.error(f"向ChromaDB添加文档时出错: {e}")
|
||||
logging.error(f"向兼容集合添加文档时出错: {e}")
|
||||
|
||||
for kb_type, docs in grouped_docs.items():
|
||||
if kb_type not in collections:
|
||||
continue
|
||||
doc_texts = [doc["text"] for doc in docs]
|
||||
metadatas = [doc["metadata"] for doc in docs]
|
||||
ids = [f"doc_{kb_type}_{i}" for i in range(len(doc_texts))]
|
||||
try:
|
||||
collections[kb_type].add(documents=doc_texts, metadatas=metadatas, ids=ids)
|
||||
logging.info(f"{kb_type} 集合入库完成,条目数: {len(doc_texts)}")
|
||||
except Exception as e:
|
||||
logging.error(f"向 {kb_type} 集合添加文档时出错: {e}")
|
||||
|
||||
# 验证一下
|
||||
count = collection.count()
|
||||
logging.info(f"数据库中现在有 {count} 个条目。")
|
||||
for name, collection in collections.items():
|
||||
try:
|
||||
count = collection.count()
|
||||
logging.info(f"集合 {name} 当前条目数: {count}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print("\n✅ 数据入库完成!")
|
||||
print(f"知识库位于: {KNOWLEDGE_BASE_DIR}")
|
||||
|
||||
6
tools/rag/knowledge_base/pattern/scene_patterns.ndjson
Normal file
@@ -0,0 +1,6 @@
|
||||
{"scene_id":"scene1_perimeter_window_ground","intent_type":"patrol_or_monitor","trigger_terms":["面前大楼","12米","外围","窗户"],"text":"地面起飞后到面前大楼约12米高度,沿外围巡查打开窗户;如发现窗户则拍照回传。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},{"type":"action","name":"takeoff","params":{"altitude":12}},{"type":"action","name":"rotate_search","params":{"target_class":"window"}},{"type":"condition","name":"object_detected","params":{"target_class":"window"}},{"type":"action","name":"take_photos","params":{"target_class":"window","track_time":10}}]}}}
|
||||
{"scene_id":"scene1_perimeter_person_air","intent_type":"patrol_or_monitor","trigger_terms":["这栋楼","空中","外围","人"],"text":"无人机已在空中,先调整高度后沿建筑外围巡查人员,发现目标后拍照。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"move_direction","params":{"direction":"up","distance":3}},{"type":"action","name":"rotate_search","params":{"target_class":"person"}},{"type":"condition","name":"object_detected","params":{"target_class":"person"}},{"type":"action","name":"take_photos","params":{"target_class":"person","track_time":10}}]}}}
|
||||
{"scene_id":"scene1_perimeter_trash_ground","intent_type":"patrol_or_monitor","trigger_terms":["面前大楼","杂物","外围"],"text":"地面起飞至指定高度后沿楼体外围侦察杂物堆积并拍照。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"takeoff","params":{"altitude":12}},{"type":"action","name":"rotate_search","params":{"target_class":"garbage"}},{"type":"condition","name":"object_detected","params":{"target_class":"garbage"}},{"type":"action","name":"take_photos","params":{"target_class":"garbage","track_time":8}}]}}}
|
||||
{"scene_id":"scene4_named_place_search_photo","intent_type":"search_and_photo","trigger_terms":["广场","查找","拍照"],"text":"前往命名地点后搜索目标并拍照。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"fly_to_waypoint","params":{"x":0,"y":0,"z":10,"acceptance_radius":2}},{"type":"action","name":"rotate_search","params":{"target_class":"person"}},{"type":"condition","name":"object_detected","params":{"target_class":"person"}},{"type":"action","name":"take_photos","params":{"target_class":"person","track_time":8}}]}}}
|
||||
{"scene_id":"scene4_named_place_monitor_return","intent_type":"return_or_land","trigger_terms":["广场南边","监控","返航"],"text":"先到达命名地点偏移区域执行监控,到时后返航。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"fly_to_waypoint","params":{"x":40,"y":80,"z":15,"acceptance_radius":2}},{"type":"action","name":"loiter","params":{"duration":300}},{"type":"action","name":"return_emergency","params":{"reason":"mission_complete"}}]}}}
|
||||
{"scene_id":"scene4_named_place_confirm_then_action","intent_type":"search_and_photo","trigger_terms":["确认后","拍照","返航"],"text":"在命名区域发现目标后等待人工确认,再执行拍照或返航。","pattern_json":{"root":{"type":"Sequence","name":"Sequence","children":[{"type":"action","name":"fly_to_waypoint","params":{"x":10,"y":10,"z":10,"acceptance_radius":2}},{"type":"action","name":"rotate_search","params":{"target_class":"car"}},{"type":"condition","name":"object_detected","params":{"target_class":"car"}},{"type":"action","name":"manual_confirmation","params":{}},{"type":"action","name":"take_photos","params":{"target_class":"car","track_time":8}}]}}}
|
||||
3
tools/rag/knowledge_base/rules/flight_rules.ndjson
Normal file
@@ -0,0 +1,3 @@
|
||||
{"rule_id":"rules_manual_confirmation","intent_type":"search_and_photo","trigger_terms":["我确认","等待确认","经允许"],"text":"仅当用户明确要求人工确认时才注入 manual_confirmation 节点;否则禁止主动添加。"}
|
||||
{"rule_id":"rules_direction_priority","intent_type":"generic_mission","trigger_terms":["东边","西边","北边","南边","米"],"text":"当只有方向+距离且无具体地点名词时,优先使用 move_direction;不要生成 fly_to_waypoint。"}
|
||||
{"rule_id":"rules_return_emergency_scope","intent_type":"return_or_land","trigger_terms":["返航","回到","去"],"text":"有明确目的地时使用 fly_to_waypoint;无明确目的地的立即返航才使用 return_emergency。"}
|
||||
@@ -1,57 +1,51 @@
|
||||
# Test & Validation Tools (Unified)
|
||||
# 测试与验证工具
|
||||
|
||||
该目录包含用于测试无人机规划系统、API 接口及 LLM 服务的集成验证工具集。
|
||||
该目录用于接口回归、批量稳定性测试和结果导出。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
使用统一入口脚本启动交互式菜单:
|
||||
## 快速开始(完整命令)
|
||||
|
||||
```bash
|
||||
python run_tests.py
|
||||
cd /home/huangfukk/DronePlanning
|
||||
source backend_service/venv/bin/activate
|
||||
python tools/test_validate/run_tests.py
|
||||
```
|
||||
|
||||
## 🛠️ 测试模式
|
||||
|
||||
### 1. 交互式单次测试 (Mode 1)
|
||||
- **场景**: 快速验证单条指令,调试 Prompt。
|
||||
- **操作**: 在终端输入自然语言指令,即时获取结果。
|
||||
- **输出**: `validation/temporary/{指令名}/`
|
||||
- `response.json`: 完整 API 响应
|
||||
- `plan.png`: 可视化任务树
|
||||
- `process.log`: 请求与响应日志
|
||||
|
||||
### 2. 批量/场景测试 (Mode 2)
|
||||
- **场景**:
|
||||
- **场景测试**: 验证一组预定义指令的正确性(默认运行 1 次)。
|
||||
- **稳定性测试**: 对同一组指令进行高频重复测试(如运行 10 次),检测成功率和延迟抖动。
|
||||
- **操作**:
|
||||
1. 选择指令文件(位于 `instructions/` 目录)。
|
||||
2. 输入每条指令的运行次数(默认 1)。
|
||||
- **输出**: `validation/{时间戳}/`
|
||||
- `test_summary.csv`: 统计摘要(成功率、平均耗时)
|
||||
- `test_details.csv`: 每次运行的详细记录
|
||||
- `instructions_backup.txt`: 本次测试使用的指令备份
|
||||
- `{指令名}/`: 包含所有运行的 `.json` 和 `.png` 产物
|
||||
|
||||
## 📂 目录结构
|
||||
## 目录说明
|
||||
|
||||
```text
|
||||
tools/test_validate/
|
||||
├── instructions/ # 指令集文件 (.txt)
|
||||
├── modules/ # 功能模块
|
||||
│ ├── api_client.py # API 客户端核心
|
||||
│ ├── interactive_test.py # 交互式测试逻辑
|
||||
│ ├── batch_runner.py # 批量测试逻辑
|
||||
│ ├── visualizer.py # 可视化工具库
|
||||
│ ├── llm_tester.py # LLM 连接测试
|
||||
│ └── drone_uploader.py # 任务上传工具
|
||||
├── validation/ # 测试产物输出
|
||||
│ ├── temporary/ # 交互式测试结果
|
||||
│ └── {时间戳}/ # 批量测试结果
|
||||
└── run_tests.py # 主程序入口
|
||||
├── instructions/ # 指令集(.txt)
|
||||
├── modules/ # API 客户端、批量执行、可视化等
|
||||
├── validation/ # 输出结果目录
|
||||
└── run_tests.py # 统一入口
|
||||
```
|
||||
|
||||
## 📄 配置文件
|
||||
## 使用方式
|
||||
|
||||
- **instructions/validate_instructions.txt**: 默认的预定义场景指令集。
|
||||
- 您可以在 `instructions/` 下添加任意 `.txt` 文件,测试时会在菜单中自动列出供选择。
|
||||
- Mode 1:交互式单条测试(快速调试)
|
||||
- Mode 2:批量测试(场景回归/稳定性)
|
||||
|
||||
输出默认在 `tools/test_validate/validation/`,包括:
|
||||
|
||||
- `test_summary.csv`
|
||||
- `test_details.csv`
|
||||
- 每条指令的 `response.json` 与 `plan.png`
|
||||
|
||||
## 典型回归流程
|
||||
|
||||
```bash
|
||||
cd /home/huangfukk/DronePlanning
|
||||
source backend_service/venv/bin/activate
|
||||
|
||||
# 先确保后端已启动
|
||||
./start_all.sh status
|
||||
|
||||
# 运行测试菜单
|
||||
python tools/test_validate/run_tests.py
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
- 后端未启动:先执行 `./start_all.sh start` 或 `./start_all_vllm.sh start`
|
||||
- API 超时:查看 `logs/fastapi.log` 与模型日志
|
||||
- 结果目录为空:确认菜单中已实际执行测试轮次
|
||||
|
||||
BIN
tools/test_validate/__pycache__/run_tests.cpython-313.pyc
Normal file
BIN
tools/test_validate/__pycache__/test_validity.cpython-313.pyc
Normal file
@@ -1,10 +1,10 @@
|
||||
无人机当前在地面,到广场查找穿黄色衣服的男子,找到后近距离拍照。
|
||||
无人机当前在空中,回到广场,对戴帽子的人进行拍照。
|
||||
无人机当前在空中,去广场西边200米,对过往的公交车拍张照,然后返航。
|
||||
无人机当前在地面,到广场查找绿色公交车,看见了拍个照片。
|
||||
无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。
|
||||
无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。
|
||||
无人机当前在地面,到广场边上的施工区域内,发现未戴安全帽的飞近后拍照。
|
||||
无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。
|
||||
无人机当前在地面,快速去往东边60米,对身穿白色衣服,头戴帽子正在挟持他人的人进行拍照。
|
||||
无人机当前在空中,离黑色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。
|
||||
无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。
|
||||
无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。
|
||||
无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。
|
||||
无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。
|
||||
无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。
|
||||
无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。
|
||||
无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。
|
||||
无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。
|
||||
无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。
|
||||
无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。
|
||||
@@ -0,0 +1,10 @@
|
||||
无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。
|
||||
无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。
|
||||
无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。
|
||||
无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。
|
||||
无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。
|
||||
无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。
|
||||
无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。
|
||||
无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。
|
||||
无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。
|
||||
无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。
|
||||
@@ -0,0 +1,11 @@
|
||||
instruction,run_id,success,latency,error
|
||||
无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。,1,True,13.293935775756836,
|
||||
无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。,1,True,10.266583442687988,
|
||||
无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。,1,True,11.274949550628662,
|
||||
无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。,1,True,1.6364917755126953,
|
||||
无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。,1,True,10.27147102355957,
|
||||
无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。,1,True,13.89813232421875,
|
||||
无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。,1,True,3.24064302444458,
|
||||
无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。,1,True,19.873026847839355,
|
||||
无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。,1,True,13.099174499511719,
|
||||
无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。,1,True,8.792617082595825,
|
||||
|
@@ -0,0 +1,11 @@
|
||||
Instruction,Total Runs,Success Runs,Success Rate,Avg Latency
|
||||
无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。,1,1,100.0%,13.29s
|
||||
无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。,1,1,100.0%,10.27s
|
||||
无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。,1,1,100.0%,11.27s
|
||||
无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。,1,1,100.0%,1.64s
|
||||
无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。,1,1,100.0%,10.27s
|
||||
无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。,1,1,100.0%,13.90s
|
||||
无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。,1,1,100.0%,3.24s
|
||||
无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。,1,1,100.0%,19.87s
|
||||
无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。,1,1,100.0%,13.10s
|
||||
无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。,1,1,100.0%,8.79s
|
||||
|
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"error": "在3次尝试后,仍未能生成一个有效的Pytree。"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。
|
||||
无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。
|
||||
无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。
|
||||
无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。
|
||||
无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。
|
||||
无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。
|
||||
无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。
|
||||
无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。
|
||||
无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。
|
||||
无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。
|
||||
@@ -0,0 +1,11 @@
|
||||
instruction,run_id,success,latency,error
|
||||
无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。,1,True,14.206302165985107,
|
||||
无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。,1,True,9.870139360427856,
|
||||
无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。,1,True,11.775025844573975,
|
||||
无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。,1,True,10.768753051757812,
|
||||
无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。,1,True,10.050657749176025,
|
||||
无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。,1,True,13.982521057128906,
|
||||
无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。,1,True,12.104306697845459,
|
||||
无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。,1,True,21.10497808456421,
|
||||
无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。,1,True,11.49827790260315,
|
||||
无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。,1,True,9.288247346878052,
|
||||
|
@@ -0,0 +1,11 @@
|
||||
Instruction,Total Runs,Success Runs,Success Rate,Avg Latency
|
||||
无人机当前在地面,到飞行场地查找穿红色衣服的男子,找到后近距离拍照。,1,1,100.0%,14.21s
|
||||
无人机当前在空中,回到飞行场地,对戴帽子的人进行拍照。,1,1,100.0%,9.87s
|
||||
无人机当前在空中,去飞行场地西边50米,对过往穿红色衣服的人拍张照,然后返航。,1,1,100.0%,11.78s
|
||||
无人机当前在地面,到飞行场地查找长头发的人,看见了拍个照片。,1,1,100.0%,10.77s
|
||||
无人机当前在空中,搜索穿红色衣服的人,搜索到了拍张照,我确认后再决定要不要返航。,1,1,100.0%,10.05s
|
||||
无人机当前在空中,往飞行场地西边飞50米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。,1,1,100.0%,13.98s
|
||||
无人机当前在地面,到飞行场地,发现未带帽子的飞近后拍照。,1,1,100.0%,12.10s
|
||||
无人机当前在空中,紧急回到飞行场地,看见了树之后直接降落。,1,1,100.0%,21.10s
|
||||
无人机当前在地面,快速去往东边30米,有身穿白色衣服,头戴帽子的男子在挟持他人,对其进行拍照。,1,1,100.0%,11.50s
|
||||
无人机当前在空中,离白色衣服戴帽子的人太远了照片看不清,贴近到3米距离拍,拍完可以直接返航。,1,1,100.0%,9.29s
|
||||
|
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"error": "在3次尝试后,仍未能生成一个有效的Pytree。"
|
||||
}
|
||||
|
After Width: | Height: | Size: 62 KiB |
54
tools/vllm_templates/qwen3_xml_tool.jinja
Normal file
@@ -0,0 +1,54 @@
|
||||
{%- if tools %}
|
||||
<|im_start|>system
|
||||
{%- if messages[0].role == 'system' -%}
|
||||
{{ messages[0].content }}
|
||||
{%- else -%}
|
||||
You are a helpful assistant.
|
||||
{%- endif -%}
|
||||
|
||||
# Tools
|
||||
You may call one or more functions to assist with the user query.
|
||||
The function signatures are provided below:
|
||||
<tools>
|
||||
{%- for tool in tools %}
|
||||
{{ tool | tojson }}
|
||||
{%- endfor %}
|
||||
</tools>
|
||||
|
||||
When you need to call a tool, respond with XML tags in this exact format:
|
||||
<tool_call>
|
||||
<function=tool_name>
|
||||
<parameter=param1>value1</parameter>
|
||||
<parameter=param2>value2</parameter>
|
||||
</function>
|
||||
</tool_call>
|
||||
<|im_end|>
|
||||
{%- else %}
|
||||
{%- if messages[0].role == 'system' -%}
|
||||
<|im_start|>system
|
||||
{{ messages[0].content }}<|im_end|>
|
||||
{%- endif -%}
|
||||
{%- endif %}
|
||||
|
||||
{%- for message in messages %}
|
||||
{%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
|
||||
<|im_start|>{{ message.role }}
|
||||
{{ message.content }}<|im_end|>
|
||||
{%- elif message.role == "assistant" %}
|
||||
<|im_start|>assistant
|
||||
{%- if message.content %}
|
||||
{{ message.content }}
|
||||
{%- endif %}
|
||||
<|im_end|>
|
||||
{%- elif message.role == "tool" %}
|
||||
<|im_start|>user
|
||||
<tool_response>
|
||||
{{ message.content }}
|
||||
</tool_response>
|
||||
<|im_end|>
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
|
||||
{%- if add_generation_prompt %}
|
||||
<|im_start|>assistant
|
||||
{%- endif %}
|
||||