AI重构
This commit is contained in:
@@ -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__/build_knowledge_base.cpython-313.pyc
Normal file
Binary file not shown.
BIN
tools/rag/__pycache__/ingest.cpython-313.pyc
Normal file
BIN
tools/rag/__pycache__/ingest.cpython-313.pyc
Normal file
Binary file not shown.
@@ -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
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
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。"}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user