AI_Agent first commit

This commit is contained in:
charry
2025-11-12 15:49:49 +08:00
parent 5f0f725357
commit 233d5047da
77 changed files with 158454 additions and 0 deletions

70
CMakeLists.txt Normal file
View File

@@ -0,0 +1,70 @@
cmake_minimum_required(VERSION 3.0.2)
project(uav_agent)
# Find catkin macros and libraries
# Added prometheus_msgs, geometry_msgs, and message_generation
find_package(catkin REQUIRED COMPONENTS
rospy
std_msgs
prometheus_msgs
iscas_msgs
geometry_msgs
message_generation
)
# Service files to be built
add_message_files(
FILES
DetectionResult_tmp.msg
)
# add_service_files(
# FILES
# LoadMission.srv
# )
# Generate services with dependencies
# generate_messages(
# DEPENDENCIES
# prometheus_msgs
# iscas_msgs
# geometry_msgs
# )
# catkin_package() settings
# Added CATKIN_DEPENDS for runtime dependencies
catkin_package(
CATKIN_DEPENDS rospy std_msgs prometheus_msgs iscas_msgs geometry_msgs message_runtime
)
# Other install rules
# install(PROGRAMS
# AI_Agent/scripts/ai_agent/ros_uav_agent_node/uav_agent_ros_node.py
# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )
catkin_install_python(PROGRAMS
AI_Agent/scripts/ai_agent/ros_uav_agent_node/uav_agent_ros_node.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
install(DIRECTORY
launch/
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
# install(DIRECTORY
# missions/
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )
install(DIRECTORY
scripts/ai_agent/config/
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
# 8. 可选:添加测试(按需启用,新手可忽略)
# if(CATKIN_ENABLE_TESTING)
# # 示例:用 rostest 测试节点(需编写 .test 文件)
# find_package(rostest REQUIRED)
# add_rostest(test/your_test.test)
# endif()

View File

@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<launch>
<!-- #################################################################### -->
<!-- 1. 可选:加载 YAML 参数文件(若需要配置参数) -->
<!-- #################################################################### -->
<!-- 路径格式:$(find 功能包名)/config/参数文件名.yaml -->
<!--<rosparam command="load" file="$(find your_package_name)/config/params.yaml" /> -->
<!-- #################################################################### -->
<!-- 2. 启动单个 Python 节点(基础版) -->
<!-- #################################################################### -->
<node
name="uav_ai_agent_node"
pkg="uav_agent"
type="uav_agent_ros_node.py"
output="screen"
required="false"
>
<!-- 节点私有参数(脚本中用 rospy.get_param("~param_name") 获取) -->
<!-- <param name="publish_freq" value="2" type="int" /> 发布频率 2Hz -->
</node>
</launch>

0
msg/.gitkeep Normal file
View File

23
package.xml Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<package format="2">
<name>uav_agent</name>
<version>1.0.0</version>
<description>uav agent with multi-modal-large-model</description>
<maintainer email="user@example.com">user</maintainer>
<license>TODO</license>
<buildtool_depend>catkin</buildtool_depend>
<depend>rospy</depend>
<!-- <depend>py_trees</depend>
<depend>prometheus_msgs</depend>
<depend>geometry_msgs</depend>
<depend>iscas_msgs</depend> -->
<depend>std_msgs</depend>
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
</package>

View File

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python3
import os
import sys
import json
from typing import List, Dict, Any, Optional, Tuple
import base64
import json
import logging
from pathlib import Path
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
from memory_manager import Memory_Store
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
class MCP_Manager:
def __init__(self):
self.memory_store = Memory_Store()
def get_context_id(self,user_input):
if "报告" in user_input:
return "work"
pass

View File

@@ -0,0 +1,453 @@
#!/usr/bin/env python3
import os
import sys
from typing import List, Dict, Any, Optional, Tuple, Union
import base64
import json
import logging
from pathlib import Path
import time
from langchain.memory import ConversationBufferWindowMemory
from langchain.prompts import PromptTemplate
from langchain.schema import HumanMessage, AIMessage
from langchain_core.memory import BaseMemory
from langchain.schema import Document, BaseMessage, HumanMessage, AIMessage
import cv2
from std_msgs.msg import String
from sensor_msgs.msg import Image as SensorImage
from cv_bridge import CvBridge
from PIL import Image
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
from tools.core.common_functions import persist_listdata2ndjson,persist_list2json
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
#查看系统环境
print("sys path:",sys.path)
#带有聊天记录分类的历史记忆
class Memory_Store:
def __init__(self):
self.memory={}
def get_history(self,context_id):
return self.memory.get(context_id,[])
def append(self,context_id, user_input, ai_response):
if context_id not in self.memory:
self.memory[context_id]=[]
self.memory[context_id].append({"user": user_input,"ai":ai_response})
# 上下文工程实现 - 支持多模态
# class MultiModalContextMemory(BaseMemory):
class MultiModalContextMemory():
def __init__(self,
max_context_window: int = 4000,
history_cnt_lmt:int = 8 ,
is_save_long_history:bool=False,
long_history_cnt_lmt:int = 100,
long_history_file_path:str=""):
"""初始化多模态上下文管理器"""
self.max_context_window = max_context_window
self.chat_history: List[Union[BaseMessage, Dict[str, Any]]] = []
self.bridge = CvBridge()
self.history_cnt_lmt = history_cnt_lmt
# 长上下文记忆
self.is_save_long_history= is_save_long_history
self.long_history:List[Union[BaseMessage, Dict[str, Any]]] = []
self.long_history_cnt_lmt:int = long_history_cnt_lmt
# self.long_history_cnt:int = 0
self.long_history_file_path = long_history_file_path
def add_text_message(self,
message: Optional[dict[str,Any]],
is_save_long_history:bool = False) -> None:
"""
添加文本消息到历史记录
src_text_info: 文本数据信息集合 类型为dict
包含{
"data_type": 数据类型,"text",
"format": 数据格式,"ai_message",
"data": 数据, AIMessage(content=response),
"timestamp": 时间戳, time.time()
}
"""
# src_text_info={
# "data": message,
# "timestamp":
# }
self.chat_history.append(message)
self._truncate_history(self.history_cnt_lmt)
if is_save_long_history:
self.long_history.append(message)
# self.long_history_cnt = self.long_history_cnt+1
long_history_len = len(self.long_history)
if long_history_len >= self.long_history_cnt_lmt:
# 保存数据到硬盘
persist_file_path = f"{self.long_history_file_path}_{self.long_history[0]["timestamp"]}_{self.long_history[long_history_len-1]["timestamp"]}.json"
persist_list2json(data_list=self.long_history,
file_path=persist_file_path)
self.long_history.clear()
# self.long_history_cnt=0
def add_image_message(self,
src_image_info:Optional[dict[str,Any]],
is_save_long_history:bool = False
) -> None:
"""
添加图像消息到历史记录
params:
src_image_info: 图像数据信息集合 类型为dict
包含{
"data_type": 数据类型
"format": 图像的格式
"data": 图像数据
"description": 图像描述文本
"timestamp": 图像的时间戳
}
"""
# 转换为PIL图像以便后续处理
try:
# cv_image = self.bridge.imgmsg_to_cv2(src_image, desired_encoding='bgr8')
# pil_image = Image.fromarray(cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB))
# self.chat_history.append({
# "type": "image",
# "format":src_image_info["format"],
# "content": src_image_info["data"],
# "description": src_image_info["description"],
# "timestamp": src_image_info["timestamp"]
# })
self.chat_history.append(src_image_info)
self._truncate_history(self.history_cnt_lmt)
logger.info("图像已添加到对话历史")
if is_save_long_history:
self.long_history.append(src_image_info)
# self.long_history_cnt = self.long_history_cnt+1
long_history_len = len(self.long_history)
if long_history_len >= self.long_history_cnt_lmt:
# 保存数据到硬盘
persist_file_path = f"{self.long_history_file_path}_{self.long_history[0]["timestamp"]}_{self.long_history[long_history_len-1]["timestamp"]}.json"
persist_list2json(data_list=self.long_history,
file_path=persist_file_path)
self.long_history.clear()
except Exception as e:
logger.error(f"添加图像到历史记录失败: {str(e)}")
def _truncate_history(self,history_cnt_lmt) -> None:
"""截断历史记录以满足最大token限制"""
# 简单实现限制最多history_cnt_lmt轮对话/图像
if len(self.chat_history) > history_cnt_lmt:
if history_cnt_lmt > 0:
self.chat_history = self.chat_history[-history_cnt_lmt:]
else:
self.chat_history = []
return
def get_k_chat_history(self, k):
"""获取k个上文对话历史"""
E_idx = len(self.chat_history) - 1
S_idx = E_idx + 1 - k if E_idx + 1 > k else 0
context = "以下是对话历史:\n"
for i in range(S_idx,E_idx) :
item = self.chat_history[i]
# if isinstance(item, HumanMessage):
if isinstance(item, dict) \
and item["data_type"]=="text" \
and item["format"]=="human_message":
context += f"用户: {item.content}\n"
# elif isinstance(item, AIMessage):
elif isinstance(item, dict) \
and item["data_type"]=="text" \
and item["format"]=="ai_message":
context += f"助手: {item.content}\n"
elif isinstance(item, dict) \
and item["data_type"] == "image":
context += f"用户发送了一张图像: {item['description']}\n"
return context
def get_k_context_query(self, query: str,k) -> str:
"""构建k个包含历史对话和当前查询的完整上下文"""
context = self.get_k_chat_history(k)
context += f"\n当前查询: {query}\n"
return context
def get_chat_history(self) -> str:
"""获取历史上文对话"""
context = "以下是对话历史:\n"
for item in self.chat_history:
# if isinstance(item, HumanMessage):
if isinstance(item, dict) \
and item["data_type"]=="text" \
and item["format"]=="human_message":
context += f"用户: {item.content}\n"
# elif isinstance(item, AIMessage):
if isinstance(item, dict) \
and item["data_type"]=="text" \
and item["format"]=="ai_message":
context += f"助手: {item.content}\n"
elif isinstance(item, dict) \
and item["type"] == "image":
context += f"用户发送了一张图像: {item['description']}\n"
return context
def get_context_query(self, query: str) -> str:
"""构建包含历史对话和当前查询的完整上下文"""
context = self.get_chat_history()
context += f"\n当前查询: {query}\n"
return context
def get_recent_images(self, k: int = 2) -> List[Image.Image]:
"""获取最近的k张图像"""
images = []
for item in reversed(self.chat_history):
if isinstance(item, dict) and item["type"] == "image":
images.append(item["content"])
if len(images) >= k:
break
return list(reversed(images)) # 保持时间顺序
def clear(self) -> None:
"""清空对话历史"""
self.chat_history = []
logger.info("多模态对话上下文已清空")
# def save_context(self, inputs: dict[str, Any], outputs: dict[str, str]) -> None:
# """Save context from this conversation to buffer."""
# input_str, output_str = self._get_input_output(inputs, outputs)
# self.chat_history.add_messages(
# [
# HumanMessage(content=input_str),
# AIMessage(content=output_str),
# ],
# )
# async def asave_context(
# self,
# inputs: dict[str, Any],
# outputs: dict[str, str],
# ) -> None:
# """Save context from this conversation to buffer."""
# input_str, output_str = self._get_input_output(inputs, outputs)
# await self.chat_memory.aadd_messages(
# [
# HumanMessage(content=input_str),
# AIMessage(content=output_str),
# ],
# )
# def clear(self) -> None:
# """Clear memory contents."""
# self.chat_memory.clear()
# async def aclear(self) -> None:
# """Clear memory contents."""
# await self.chat_memory.aclear()
def persist_context_ndjson(self,
persist_directory:str,
rw_mode:str="w",
encoding:str="utf-8",
start:Optional[int] = None,
end:Optional[int] = None):
chat_history_len = len(self.chat_history)
if start is not None:
S_idx = start if start > 0 and start < chat_history_len else 0
else:
S_idx = 0
if end is not None:
E_idx = end if end < chat_history_len else chat_history_len - 1
else:
E_idx = chat_history_len - 1
#保存到NDJSON文件
with open(file=persist_directory, mode=rw_mode, encoding=encoding) as f:
for i in range(S_idx,E_idx) :
item = self.chat_history[i]
if isinstance(item["data"], HumanMessage):
context_line={"用户": item.content}
# context += f"用户: {item.content}\n"
elif isinstance(item["data"], AIMessage):
context_line={"助手": item.content}
# context += f"助手: {item.content}\n"
elif isinstance(item, dict) and item["type"] == "image":
# context += f"用户发送了一张图像: {item['description']}\n"
context_line={"用户": f"用户发送了一张图像: {item['description']}"}
# context_ndjson.append(context_line)
json_str = json.dumps(context_line, ensure_ascii=False)
f.write(json_str + "\n")
def load_context_ndjson(self,
file_path:str,
rw_mode:str="r",
encoding:str="utf-8",
start:Optional[int] = None,
end:Optional[int] = None):
"""
读取NDJSON文件返回解析后的字典列表每行一个字典
:param file_path: NDJSON文件路径字符串或Path对象
:return: 包含所有JSON对象的列表每个元素为dict
"""
data = []
file_path = Path(file_path) # 统一转为Path对象方便处理路径
# 检查文件是否存在
if not file_path.exists():
print(f"警告:文件不存在 - {file_path}")
return data
# 检查是否是文件(不是目录)
if not file_path.is_file():
print(f"警告:{file_path} 不是文件")
return data
# 逐行读取并解析
with open(file_path, mode=rw_mode, encoding=encoding) as f:
line_count = len(f.readlines())
if end is not None:
E_idx = end if end < line_count-1 else line_count-1
else:
E_idx = line_count-1
# line_count = len(f.readlines())
if start is not None:
S_idx = start if start > max(E_idx - self.history_cnt_lmt + 1, 0) else max(E_idx - self.history_cnt_lmt + 1, 0)
else:
S_idx = max(E_idx - self.history_cnt_lmt + 1, 0)
for line_num, line in enumerate(f, start=0): # 记录行号,方便定位错误
if line_num >= S_idx and line_num < E_idx:
line = line.strip() # 去除首尾空白(如换行符、空格)
# 跳过空行(避免解析空字符串报错)
if not line:
continue
try:
# 将JSON字符串解析为字典添加到列表
json_obj = json.loads(line)
data.append(json_obj)
except json.JSONDecodeError as e:
# 捕获无效JSON格式的错误提示行号便于排查
print(f"警告:第 {line_num} 行JSON格式无效已跳过 - 错误:{e}")
return data
def load_context_json(self,
file_path:str,
rw_mode:str="r",
encoding:str="utf-8",
start:Optional[int] = None,
end:Optional[int] = None):
"""
读取NDJSON文件返回解析后的字典列表每行一个字典
:param file_path: NDJSON文件路径字符串或Path对象
:return: 包含所有JSON对象的列表每个元素为dict
"""
data = []
file_path = Path(file_path) # 统一转为Path对象方便处理路径
# 检查文件是否存在
if not file_path.exists():
print(f"警告:文件不存在 - {file_path}")
return data
# 检查是否是文件(不是目录)
if not file_path.is_file():
print(f"警告:{file_path} 不是文件")
return data
try:
# 逐行读取并解析
with open(file_path, mode=rw_mode, encoding=encoding) as f:
src_data = json.load(f)
src_data_len = len(src_data)
if end is not None:
E_idx = end if end < src_data_len-1 else src_data_len-1
else:
E_idx = src_data_len-1
# line_count = len(f.readlines())
if start is not None:
S_idx = start if start > max(E_idx - self.history_cnt_lmt + 1, 0) else max(E_idx - self.history_cnt_lmt + 1, 0)
else:
S_idx = max(E_idx - self.history_cnt_lmt + 1, 0)
if isinstance(src_data, dict):
data.append(src_data)
print("JSON是单个dict已添加到list")
# 情况2JSON内容是list → 检查元素是否为dict过滤非dict元素
elif isinstance(src_data, list):
# 遍历列表只保留dict类型的元素
for item in data:
if isinstance(item, dict):
data.append(item)
else:
print(f"警告列表中存在非dict元素 {item},已跳过")
print(f"JSON是list共保留 {len(src_data)} 个dict元素")
# 其他情况如JSON是字符串、数字等无法转为dict列表
else:
print(f"错误JSON内容类型为 {type(data)}无法提取dict对象")
except json.JSONDecodeError as e:
print(f"错误JSON格式无效 - {e}(文件:{file_path}")
except Exception as e:
print(f"加载文件失败:{e}(文件:{file_path}")
return data

View File

@@ -0,0 +1,454 @@
#!/usr/bin/env python3
import os
import sys
import json
from typing import List, Dict, Any, Optional, Tuple
import magic # 用于识别文件类型(辅助文件搜索工具)
import base64
import logging
from pathlib import Path
from langchain.agents import initialize_agent, AgentType
from langchain.tools import BaseTool, Tool
from langchain.llms import LlamaCpp
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.vectorstores import Chroma
from langchain.embeddings import LlamaCppEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import (
TextLoader, UnstructuredFileLoader, PDFMinerLoader,
UnstructuredMarkdownLoader, Docx2txtLoader
)
from langchain.chains import RetrievalQA
from langchain.memory import ConversationBufferWindowMemory
from langchain.schema import HumanMessage, AIMessage
from langchain.output_parsers import PydanticOutputParser
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from llama_cpp import Llama, LlamaGrammar
from requests_toolbelt import ImproperBodyPartContentException
import rospy # type: ignore
import threading
import time
from std_msgs.msg import String # type: ignore
from sensor_msgs.msg import Image as Sensor_Image # type: ignore
from cv_bridge import CvBridge, CvBridgeError # pyright: ignore[reportMissingImports]
import cv2
import numpy as np
import asyncio
from PIL import Image as PIL_Image
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
from tools.mcp_clients import MCP_Clients
from tools.core.common_functions import read_json_file
from pnc.pnc import PNC
from models.models_manager import Models_Mag
from agents.memory_manager import MultiModalContextMemory
from agents.prompt_manager import Prompt_Manager
from agents.multimodal_processor import MultimodalProcessor
from agents.rag_manager import RAG_Mag
from models.model_definition import (TextInferenceRequest,MultimodalInferenceRequest,InferenceRequest,InferenceResponse,HealthResponse)
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
class Image_Info:
type: str
format: str
data: str
width: int
height: int
original_encoding: str
timestamp: float
# ------------------------------
# 主Agent类
# ------------------------------
class MMLM_Agent:
"""多模态 Agent整合所有核心功能"""
def __init__(self,
# vlm_model_path: str,
# embedding_model_path: str
# max_context_window: int = 5,
# max_tokens: int = 512
):
"""
初始化多模态UAV Agent
参数:
vlm_model_path: VLM模型路径
embedding_model_path: 嵌入模型路径
# max_context_window: 记忆窗口大小
# max_tokens: 生成回答的最大token数
"""
# # 初始化组件
# self.mcp = MCPProtocol(max_context_length=4096)
# self.multimodal_processor = MultimodalProcessor()
# self.rag_processor = RAGProcessor(
# embedding_model_path=embedding_model_path,
# persist_directory=persist_directory
# )
# 声明对话记忆
self.memory_mag = None
#声明模型管理器
self.model_mag=None
#声明mcp_client
self.mcp_clients_mag = None
#声明pnc_mag
self.pnc_mag=None
#声明prompt_mag
self.prompt_mag=None
#agent模态数据处理器
self.multimodal_processor=None
#agent的RAG管理工具
self.rag_mag=None
#agent配置文件
self.agent_config = None
# self.vlm_model_path = vlm_model_path
# self.embedding_model_path = embedding_model_path
# self.max_context_window = max_context_window
# self.max_tokens = max_tokens
logger.info("多模态UAV Agent初始化完成")
def parse_config(self,config_json_file):
"""解析agent的config文件"""
self.agent_config= read_json_file(file_path=config_json_file)
print("finished!")
def init_memory(self,agent_config):
"""初始化历史记忆的memory"""
try:
if "mmcm" == agent_config["memory"]["history_memory_type"]:
self.memory_mag = MultiModalContextMemory(max_context_window=agent_config["memory"]["max_context_window"],
history_cnt_lmt=agent_config["memory"]["history_cnt_lmt"],
is_save_long_history=agent_config["memory"]["is_save_long_history"],
long_history_cnt_lmt=agent_config["memory"]["long_history_cnt_lmt"],
long_history_file_path=agent_config["memory"]["long_history_file_path"])
elif "lccbwm" == agent_config["memory"]["history_memory_type"]:
self.memory_mag = ConversationBufferWindowMemory(
k = agent_config["memory"]["max_context_window"],
memory_key=agent_config["memory"]["lccbwm"]["agent_memory_key"],
return_messages=agent_config["memory"]["lccbwm"]["return_messages"],
output_key=agent_config["memory"]["lccbwm"]["output_key"]
)
logger.info(msg=f"memory初始化成功")
return True
except Exception as e:
logger.error(f"memory初始化失败: {str(e)}")
return False
def init_model_mag(self,agent_config):
try:
# 初始化model mag
self.model_mag = Models_Mag()
# if agent_config["model_mag"]["is_model_server"]:
# self.model_mag.init_model_server(model_config=agent_config["model_mag"]["model_config"])
# else:
# self.model_mag.init_model_client(model_config=agent_config["model_mag"]["model_config"])
self.model_mag.init_model_interface(model_config=agent_config["model_mag"]["model_config"],
is_model_server=agent_config["model_mag"]["is_model_server"])
logger.info(msg=f"model_mag初始化成功")
return True
except Exception as e:
logger.error(f"model_mag初始化失败: {str(e)}")
return False
def init_mcp_clients_mag(self):
try:
# 初始化model mag
self.mcp_clients_mag = MCP_Clients()
logger.info(msg=f"mcp_clients_mag初始化成功")
return True
except Exception as e:
logger.error(f"mcp_clients_mag初始化失败: {str(e)}")
return False
def init_pnc_mag(self):
try:
# 初始化pnc_mag
self.pnc_mag=PNC()
logger.info(msg=f"pnc_mag初始化成功")
return True
except Exception as e:
logger.error(f"pnc_mag初始化失败: {str(e)}")
return False
def init_prompt_mag(self):
try:
# 初始化prompt_mag
self.prompt_mag=Prompt_Manager()
logger.info(msg=f"prompt_mag初始化成功")
return True
except Exception as e:
logger.error(f"prompt_mag初始化失败: {str(e)}")
return False
def init_multimodal_processor(self):
try:
# 初始化multimodal_processor
self.multimodal_processor=MultimodalProcessor()
logger.info(msg=f"multimodal_processor初始化成功")
return True
except Exception as e:
logger.error(f"multimodal_processor初始化失败: {str(e)}")
return False
def init_rag_mag(self,agent_config):
try:
# 初始化rag_mag
self.rag_mag=RAG_Mag()
self.rag_mag.set_components(agent_config["rag_mag"])
logger.info(msg=f"rag_mag初始化成功")
return True
except Exception as e:
logger.error(f"rag_mag初始化失败: {str(e)}")
return False
def init_components(self,agent_config):
try:
self.init_memory(agent_config=agent_config)
self.init_model_mag(agent_config=agent_config)
self.init_mcp_clients_mag()
self.init_pnc_mag()
self.init_prompt_mag()
self.init_multimodal_processor()
self.init_rag_mag(agent_config=agent_config)
logger.info(f"agent_components初始化成功")
return True
except Exception as e:
logger.error(f"agent_components初始化失败:{str(e)}")
return False
# def retrieve_relevant_info(self, query: str, k: int = 3, score_threshold: float = 0.2) -> List[Dict[str, Any]]:
# """
# 检索与查询相关的信息
# 参数:
# query: 查询文本
# k: 最多返回的结果数量
# score_threshold: 相关性分数阈值
# 返回:
# 包含相关文档内容和分数的列表
# """
# try:
# # 执行相似性搜索,返回带分数的结果
# docs_and_scores = self.vectorstore.similarity_search_with_score(query, k=k)
# # 过滤并格式化结果
# results = []
# for doc, score in docs_and_scores:
# if score < score_threshold: # 分数越低表示越相似
# results.append({
# "content": doc.page_content,
# "metadata": doc.metadata,
# "similarity_score": float(score)
# })
# logger.info(f"检索到 {len(results)} 条相关信息 (k={k}, 阈值={score_threshold})")
# return results
# except Exception as e:
# logger.error(f"检索相关信息失败: {str(e)}")
# return []
async def process_query(self, query_info: Optional[dict[str,Any]],
image_data: Optional[dict[str,Any]] = None,
max_tokens:int = 300,
temperature: float = 0.7,
top_p: float = 0.95,
stop: Optional[List[str]] = None,
is_use_chat_history:bool = False,
is_use_rag:bool = False,
is_save_history:bool = False) :
"""
处理用户查询,可包含图像数据
参数:
query_info: 用户查询的基本信息
image_data: 可选的图像数据字典
返回:
生成的回答
"""
try:
## 1.处理文本查询
processed_text = self.multimodal_processor.process_text(query_info["user_query"])
# self.mcp.add_message("user", processed_text["data"], "text")
## 2.处理图像数据(如果有)
# if image_data:
# self.mcp.add_message("user", "图像数据", "image")
# logger.info("已添加图像数据到上下文")
## 3.获取对话历史
# chat_history = self.memory.load_memory_variables({})["chat_history"]
# chat_history_str = "\n".join([
# f"{'用户' if isinstance(msg, HumanMessage) else '助手'}: {msg.content}"
# for msg in chat_history
# ])
if is_use_chat_history:
chat_history = self.memory_mag.get_chat_history()
# chat_history_str = "\n".join([
# f"{'用户' if isinstance(msg, HumanMessage) else '助手'}: {msg.content}"
# for msg in chat_history
# ])
## 4.增强检索相关信息
if is_use_rag:
relevant_info = self.rag_mag.retrieve_relevant_info(processed_text)
context_str = "\n\n".join([item["content"] for item in relevant_info])
## 5.构建提示
# prompt = self.prompt_template.format(
# context=context_str,
# question=query,
# chat_history=chat_history_str
# )
# self.prompt_mag.set_conversation_prompt_template(input_variables,conversation_template)
# "model_role","field","context", "chat_history", "question"
final_conversation_prompt_template = self.prompt_mag.create_final_conversation_prompt_template()
final_conversation_prompt = final_conversation_prompt_template.format(
model_role=query_info["model_role"],
field=query_info["field"],
context = context_str,
chat_history=chat_history,
question=processed_text
)
logger.debug(f"生成提示: {final_conversation_prompt[:200]}...") # 只显示前200字符
## 6.调用VLM生成回答
# output = self.llm(
# prompt=conversation_prompt,
# max_tokens=self.max_tokens,
# stop=["\n用户:", "\n助手:", "<|end_of_solution|>"],
# temperature=0.7, # 控制输出随机性
# top_p=0.9,
# echo=False
# )
if image_data: #base64
multi_modal_request = MultimodalInferenceRequest(user_prompt=final_conversation_prompt,
image_data=[image_data["data"]],
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
stop = stop)
output = self.model_mag.models_interface.multimodal_inference(request=multi_modal_request)
else:
text_request = TextInferenceRequest(user_prompt=final_conversation_prompt,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
stop = stop)
output=self.model_mag.models_interface.text_inference(request=text_request)
# 提取回答
response = output["result"]
# 更新记忆和上下文
if is_save_history:
if image_data:
image_info = {
"format": "base64",
"data": image_data["data"],
"description": "",
"timestamp": time.time()
}
self.memory_mag.add_image_message(image_info)
# self.memory.save_context(
# {"input": processed_text},
# {"output": response}
# )
human_text_info={
"data_type":"text",
"format":"human_message",
"data": HumanMessage(content=processed_text),
"timestamp": time.time()
}
self.memory_mag.add_text_message(human_text_info)
if response:
ai_text_info={
"data_type":"text",
"format":"ai_message",
"data": AIMessage(content=response),
"timestamp": time.time()
}
self.memory_mag.add_text_message(ai_text_info)
# self.mcp.add_message("assistant", response, "text")
logger.info(f"生成回答,长度: {len(response)}")
return response
except Exception as e:
error_msg = f"处理查询时出错: {str(e)}"
logger.error(error_msg)
# self.mcp.add_message("assistant", error_msg, "text")
return error_msg
def add_knowledge_document(self, file_path: str) -> Tuple[bool, str]:
"""添加知识文档到RAG系统"""
return self.rag_mag.add_document(file_path)
def clear_context(self) -> None:
"""清除对话上下文和记忆"""
# self.mcp.clear_context()
self.memory_mag.clear()
logger.info("已清除所有对话上下文和记忆")

View File

@@ -0,0 +1,137 @@
#!/usr/bin/env python3
import os
import sys
import json
from typing import List, Dict, Any, Optional, Tuple
import logging
import rospy # type: ignore
import threading
import time
from std_msgs.msg import String # type: ignore
from sensor_msgs.msg import Image as Sensor_Image # type: ignore
from cv_bridge import CvBridge, CvBridgeError # pyright: ignore[reportMissingImports]
import cv2
import numpy as np
import magic # 用于识别文件类型(辅助文件搜索工具)
import base64
import asyncio
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# ------------------------------
# 多模态处理器
# ------------------------------
class MultimodalProcessor:
"""处理多模态输入输出,实现不同类型数据的转换和处理"""
def __init__(self):
self.bridge = CvBridge()
self.supported_image_formats = ['bgr8', 'rgb8', 'mono8']
def ros_image2dict(self, image_msg: Sensor_Image) -> Optional[Dict[str, Any]]:
"""
将ROS图像消息转换为字典格式便于在上下文中存储
参数:
image_msg: ROS的Image消息
返回:
包含图像信息的字典或None(转换失败时)
"""
try:
# 尝试转换为OpenCV格式
if image_msg.encoding in self.supported_image_formats:
cv_image = self.bridge.imgmsg_to_cv2(image_msg, desired_encoding=image_msg.encoding)
else:
# 尝试默认转换
cv_image = self.bridge.imgmsg_to_cv2(image_msg)
logger.warning(f"转换不支持的图像编码: {image_msg.encoding}")
# 图像预处理:调整大小以减少数据量
max_dim = 300
h, w = cv_image.shape[:2]
if max(h, w) > max_dim:
scale = max_dim / max(h, w)
cv_image = cv2.resize(
cv_image,
(int(w * scale), int(h * scale)),
interpolation=cv2.INTER_AREA
)
# 转换为JPEG并编码为base64
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 80]
_, buffer = cv2.imencode('.jpg', cv_image, encode_param)
img_base64 = base64.b64encode(buffer).decode('utf-8')
return {
"type": "image",
"format": "JPRG_BASE64",
"data": img_base64,
"width": cv_image.shape[1],
"height": cv_image.shape[0],
"original_encoding": image_msg.encoding,
"timestamp": image_msg.header.stamp.to_sec()
}
except CvBridgeError as e:
logger.error(f"CV桥接错误: {str(e)}")
except Exception as e:
logger.error(f"图像处理错误: {str(e)}")
return None
def dict2ros_image(self, image_dict: Dict[str, Any]) -> Optional[Sensor_Image]:
"""
将图像字典转换回ROS Image消息
参数:
image_dict: 包含图像信息的字典
返回:
ROS Image消息或None(转换失败时)
"""
try:
if image_dict.get("type") != "image" or "data" not in image_dict:
logger.error("无效的图像字典格式")
return None
# 解码base64数据
img_data = base64.b64decode(image_dict["data"])
np_arr = np.frombuffer(img_data, np.uint8)
cv_image = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
# 转换为ROS Image消息
return self.bridge.cv2_to_imgmsg(cv_image, encoding="bgr8")
except Exception as e:
logger.error(f"图像转换为ROS消息失败: {str(e)}")
return None
def process_text(self, text: str) -> Dict[str, Any]:
"""
处理文本输入,进行基本的清洗和格式化
参数:
text: 输入文本
返回:
包含处理后文本的字典
"""
# 基本文本清洗
cleaned_text = text.strip()
# 移除多余空行
cleaned_text = "\n".join([line.strip() for line in cleaned_text.splitlines() if line.strip()])
return {
"type": "text",
"data": cleaned_text,
"length": len(cleaned_text),
"timestamp": time.time()
}

View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python3
import os
import sys
import json
from typing import List, Dict, Any, Optional, Tuple
import magic # 用于识别文件类型(辅助文件搜索工具)
import base64
import logging
from pathlib import Path
from langchain.prompts import PromptTemplate
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
class Prompt_Manager:
def __init__(self):
# self.final_conversation_prompt_template = None
pass
def create_object_detect_prompt_template(self):
"""
设置
input_variables: 输入参数
template: 模板样式
"""
try:
input_variables= ["object_description",]
obj_detect_template = """检测图片中{object_description},并给出对应的2D框坐标。"""
# 初始化memory
obj_detect_prompt_template = PromptTemplate(
input_variables= input_variables,
template= obj_detect_template
)
logger.info(msg=f"obj_detect_prompt_template设置成功")
return obj_detect_prompt_template
except Exception as e:
logger.error(f"obj_detect_prompt_template设置失败: {str(e)}")
return None
def create_final_conversation_prompt_template(self):
"""
设置
input_variables: 输入参数
template: 模板样式
"""
try:
input_variables= ["model_role","field","context", "chat_history", "question"]
final_template = """您是一位{model_role},擅长解决{field}的问题,您需要基于提供的上下文信息和对话历史来回答用户的问题。
上下文信息:
{context}
对话历史:
{chat_history}
用户当前的问题:
{question}
请根据以上信息,用简洁、准确的语言回答用户的问题。如果上下文信息不足以回答,请说明这一点。
回答:"""
# 初始化memory
final_conversation_prompt_template = PromptTemplate(
input_variables= input_variables,
template= final_template
)
logger.info(msg=f"prompt_template设置成功")
return final_conversation_prompt_template
except Exception as e:
logger.error(f"prompt_template设置失败: {str(e)}")
return None
# 创建优化的提示词
def create_chat_prompt_template(self,prompt:str,variable_name:str):
"""创建智能提示词引导AI合理使用工具
参数:
prompt: 提示词,例如"你是一个专业的智能助手,具备多种工具能力,能够帮助用户完成各类任务。
可用工具及使用场景:
- terminal: 执行系统命令、文件操作、程序运行
- app_analysis: 职业分析、应用程序分析、用户画像
- browser: 网页控制、自动化操作、信息采集
- weather: 天气查询、气象信息获取
工具使用原则:
1. 只有用户明确需要执行具体操作时才使用工具
2. 对于一般性对话、概念解释、建议咨询等,直接回答
3. 优先选择最合适的工具来完成任务
4. 如果不确定是否需要工具,优先选择直接回答
请保持自然、友好的对话风格,准确理解用户真实意图,明智地选择是否使用工具。"
variable_name: 新增的变量的名称例如“message”
"""
prompt_template = ChatPromptTemplate.from_messages([
("system", prompt),
MessagesPlaceholder(variable_name=variable_name)
])
return prompt_template

View File

@@ -0,0 +1,321 @@
#!/usr/bin/env python3
import os
import sys
import json
from typing import List, Dict, Any, Optional, Tuple
import magic # 用于识别文件类型(辅助文件搜索工具)
import base64
import logging
from pathlib import Path
from langchain.chains import LLMChain
# from langchain.vectorstores import Chroma
from langchain.embeddings import LlamaCppEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import (
TextLoader, UnstructuredFileLoader, PDFMinerLoader,
UnstructuredMarkdownLoader, Docx2txtLoader
)
from langchain.chains import RetrievalQA
from langchain_chroma import Chroma
import threading
import time
import cv2
import numpy as np
import asyncio
from PIL import Image as PIL_Image
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
if project_root not in sys.path:
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
from scripts.ai_agent.tools.memory_mag.rag.rag import (set_embeddings,load_vector_database,
set_text_splitter,set_document_loaders,
retrieve_relevant_info)
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# ------------------------------
# RAG (检索增强生成) 处理器
# ------------------------------
class RAG_Mag:
"""实现检索增强生成功能,管理知识库和检索过程"""
def __init__(self):
"""
初始化RAG处理器
参数:
embedding_model_path: 嵌入模型路径
persist_directory: 向量数据库持久化目录
chunk_size: 文本分割大小
chunk_overlap: 文本块重叠大小
"""
# 初始化嵌入模型
# self.embedding_model_path = embedding_model_path
# self.embeddings = LlamaCppEmbeddings(
# model_path=embedding_model_path,
# n_ctx=n_ctx,
# n_threads=n_threads
# )
self.embeddings = None
# 确保持久化目录存在
# os.makedirs(persist_directory, exist_ok=True)
# 初始化向量存储
# self.vectorstore = Chroma(
# persist_directory=persist_directory,
# embedding_function=self.embeddings,
# client_settings={"persist_directory": persist_directory}
# )
self.vectorstore = None
# 初始化文本分割器
# self.text_splitter = RecursiveCharacterTextSplitter(
# chunk_size=chunk_size,
# chunk_overlap=chunk_overlap,
# length_function=len,
# separators=["\n\n", "\n", " ", ""]
# )
self.text_splitter = None
# 文档加载器映射
# self.document_loaders = {
# '.txt': TextLoader,
# '.pdf': PDFMinerLoader,
# '.md': UnstructuredMarkdownLoader,
# '.docx': Docx2txtLoader,
# # 可以添加更多文档类型
# }
self.document_loaders = None
logger.info(f"RAG_Manager初始化完成")
# def set_embeddings(self,
# embedding_model_name,
# embedding_type:str,
# model_config:Optional[dict[str,Any]]):
# """初始化embeddings"""
# try:
# self.embeddings = set_embeddings(embedding_model_name=embedding_model_name,
# embedding_type=embedding_type,
# model_config=model_config)
# except Exception as e:
# logger.error(f"embeddings初始化失败:{str(e)}")
# return None
def set_vectorstore(self,
embeddings,
client_settings:Optional[dict[str,Any]]={},
vdb_dir: str = "./chroma_db"
):
"""初始化vectorstore"""
try:
# 确保持久化目录存在
# os.makedirs(persist_directory, exist_ok=True)
# 初始化向量存储
# 检查是否需要覆盖现有数据库
if os.path.exists(vdb_dir) :
print(f"加载现有向量数据库: {vdb_dir}")
vectorstore = Chroma(
embedding_function=embeddings,
client_settings=client_settings,
persist_directory=vdb_dir
)
return vectorstore
else:
logger.error(f"vectorstore初始化失败,没有向量数据库")
return None
except Exception as e:
logger.error(f"vectorstore初始化失败:{str(e)}")
return None
# def set_text_splitter(self,
# chunk_size: int = 500,
# chunk_overlap: int = 50):
# try:
# text_splitter = RecursiveCharacterTextSplitter(
# chunk_size=chunk_size,
# chunk_overlap=chunk_overlap,
# length_function=len,
# separators=["\n\n", "\n", " ", ""]
# )
# return text_splitter
# except Exception as e:
# logger.error(f"text_splitter初始化失败: {str(e)}")
# return None
# def set_document_loaders(self):
# try:
# document_loaders = {
# '.txt': TextLoader,
# '.pdf': PDFMinerLoader,
# '.md': UnstructuredMarkdownLoader,
# '.docx': Docx2txtLoader,
# # 可以添加更多文档类型
# }
# return document_loaders
# except Exception as e:
# logger.error(f"document_loaders初始化失败: {str(e)}")
# return None
def set_components(self,
rag_config:Optional[dict[str,Any]],
model_config:Optional[dict[str,Any]]):
try:
self.embeddings=set_embeddings(embedding_model_path=rag_config["embedding_model_path"],
embedding_type=rag_config["embedding_type"],
model_config=model_config)
# self.set_embeddings(embedding_framework_type=rag_config["embedding_framework_type"],
# embedding_model_path=rag_config["embedding_model_path"],
# n_ctx=rag_config["n_ctx"],
# n_threads=rag_config["n_threads"],
# n_gpu_layers=rag_config["n_gpu_layers"],
# verbose=verbose)
self.vectorstore =load_vector_database(embeddings=self.embeddings,
vdb_dir=rag_config["vectorstore_persist_directory"],
collection_name=rag_config["collection_name"]
# client_settings={"persist_directory": rag_config["vectorstore_persist_directory"]}
)
self.text_splitter =set_text_splitter(chunk_size=rag_config["text_splitter"]["chunk_size"],
chunk_overlap=rag_config["text_splitter"]["chunk_overlap"])
self.document_loaders=set_document_loaders()
logger.info(f"set_components成功。")
return True
except Exception as e:
logger.error(f"set_components失败: {str(e)}")
return False
#创建chromdb向量数据库
def create_vector_database(embeddings,
collection_name,
documents,
db_dir,
overwrite=False):
"""创建或加载向量数据库"""
# embeddings = LlamaCppEmbeddings(
# model_path = embedding_model_name,
# n_threads=n_threads # 根据 CPU 核心数调整(如 8 核心设为 4 或 8
# )
# print(f"已加载嵌入模型: {embedding_model_name}")
try:
# 检查是否需要覆盖现有数据库
if os.path.exists(path=db_dir) and not overwrite:
print(f"加载现有向量数据库: {db_dir}")
vector_db = Chroma(
persist_directory = db_dir,
embedding_function=embeddings,
collection_name=collection_name
)
else:
print(f"创建新的向量数据库: {db_dir}")
vector_db = Chroma.from_documents(
documents=documents,
embedding=embeddings,
persist_directory=db_dir,
collection_name=collection_name
)
vector_db.persist()
return vector_db
except Exception as e:
logger.error(f"create_vector_database失败: {str(e)}")
return None
def add_document(self, file_path: str) -> Tuple[bool, str]:
"""
添加文档到知识库
参数:
file_path: 文档文件路径
返回:
(是否成功, 消息)
"""
if not os.path.exists(file_path):
error_msg = f"文件不存在: {file_path}"
logger.error(error_msg)
return False, error_msg
# 获取文件扩展名
_, ext = os.path.splitext(file_path.lower())
if ext not in self.document_loaders:
error_msg = f"不支持的文件类型: {ext},支持的类型: {list(self.document_loaders.keys())}"
logger.error(error_msg)
return False, error_msg
try:
# 加载文档
loader_class = self.document_loaders[ext]
loader = loader_class(file_path)
documents = loader.load()
logger.info(f"加载文档: {file_path}, 页数: {len(documents)}")
# 分割文档
splits = self.text_splitter.split_documents(documents)
logger.info(f"文档分割为 {len(splits)} 个片段")
# 添加到向量存储
self.vectorstore.add_documents(splits)
self.vectorstore.persist()
success_msg = f"文档成功添加到知识库: {file_path},生成 {len(splits)} 个向量"
logger.info(success_msg)
return True, success_msg
except Exception as e:
error_msg = f"添加文档失败: {str(e)}"
logger.error(error_msg)
return False, error_msg
def retrieve_relevant_info(self, query: str, k: int = 3, score_threshold: float = 0.2) -> List[Dict[str, Any]]:
"""
检索与查询相关的信息
参数:
query: 查询文本
k: 最多返回的结果数量
score_threshold: 相关性分数阈值
返回:
包含相关文档内容和分数的列表
"""
return retrieve_relevant_info(vectorstore=self.vectorstore,
query=query,
k=k,
score_threshold=score_threshold)
def get_retriever(self, search_kwargs: Dict[str, Any] = None) -> Any:
"""获取检索器对象用于LangChain的RetrievalQA链"""
if search_kwargs is None:
search_kwargs = {"k": 3}
return self.vectorstore.as_retriever(search_kwargs=search_kwargs)

View File

@@ -0,0 +1,70 @@
#python==3.10.5
huggingface_hub==0.34.4
scikit-learn>=1.2.2
scipy==1.15.3
#torch==2.7.1
#torchvision>=0.15
#5090显卡安装torch命令 pip install torch==2.7.1 torchvision==0.22.1 torchaudio==2.7.1 --index-url https://download.pytorch.org/whl/cu128
tqdm==4.67.1
# 核心LangChain框架
langchain>=0.3.0,<0.4.0
langchain-core>=0.3.0,<0.4.0
# LangGraph - ReAct Agent支持
langgraph>=0.5.0,<0.6.0
# MCP集成适配器
langchain-mcp-adapters>=0.1.0,<0.2.0
# Ollama集成替代DeepSeek
# langchain-ollama>=0.3.0,<0.4.0
# OpenAI集成
langchain-openai>=0.3.0,<0.4.0
langchain_chroma==0.2.5
# FastMCP框架
fastmcp>=2.0.0,<3.0.0
# MCP协议核心
mcp>=1.9.0,<2.0.0
# 基础依赖
pydantic>=2.0.0,<3.0.0
httpx>=0.25.0,<1.0.0
anyio>=4.0.0,<5.0.0
langchain-community==0.3.29
openai==1.105.0
chromadb==1.0.20
# 安装llama-cpp-python的命令
# # 确保已安装系统级 CUDA 驱动和 Toolkit如 CUDA 12.2
# 执行以下命令开启 GPU 编译
#CMAKE_ARGS="\
#-DGGML_BLAS=on \
#-DGGML_BLAS_VENDOR=OpenBLAS \
#-DBLAS_LIBRARIES=/usr/lib/x86_64-linux-gnu/libopenblas.so \
#-DBLAS_INCLUDE_DIRS=/usr/include/openblas \
#-DOpenMP_C_FLAGS=-fopenmp \
#-DOpenMP_CXX_FLAGS=-fopenmp \
#-DOpenMP_C_LIBRARY=/usr/lib/x86_64-linux-gnu/libgomp.so \
#-DOpenMP_CXX_LIBRARY=/usr/lib/x86_64-linux-gnu/libgomp.so \
#-DGGML_CUDA=on \ # 新参数:启用 CUDA 加速(替换原 LLAMA_CUBLAS=on
#-DCUDAToolkit_ROOT=/usr/local/cuda" \ # 保持 CUDA 路径不变
#pip install --upgrade --force-reinstall llama-cpp-python \
#-i https://pypi.tuna.tsinghua.edu.cn/simple
fastapi==0.115.14
fastapi-utils==0.6.0
opencv-python==4.12.0.88
opencv-contrib-python==4.12.0.88
rospkg==1.6.0
catkin-tools==0.9.5
empy==4.2
sentence-transformers==5.0.0

View File

@@ -0,0 +1,58 @@
{
"memory":{
"history_memory_type": "mmcm",
"max_context_window": 4000,
"history_cnt_lmt": 10,
"is_save_long_history":0,
"long_history_cnt_lmt":100,
"long_history_file_path":"/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/memory/long_term_memory/long_history",
"lccbwm":{
"agent_memory_key": "chat_history",
"return_messages": 1,
"output_key": "answer"
}
},
"model_mag":{
"is_model_server": 0,
"model_inference":{
"max_tokens": 300,
"temperature":0.7,
"top_p":0.95
},
"model_config":{
"models_server":
{
"model_inference_framework_type":"llama_cpp",
"multi_modal":1,
"chat_handler": "Qwen25VLChatHandler",
"vlm_model_path":"/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen2.5-VL-3B-Instruct-GGUF/Qwen2.5-VL-3B-Instruct-Q8_0.gguf",
"mmproj_model_path":"/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen2.5-VL-3B-Instruct-GGUF/mmproj-model-f16.gguf",
"n_ctx":30720,
"n_threads":4,
"n_gpu_layers":40,
"n_batch":24,
"n_ubatch":24,
"verbose":0,
"model_server_host":"localhost",
"model_server_port":8000,
"model_controller_workers":1
},
"models_client":
{
"server_url":"http://localhost:8000"
}
}
},
"rag_mag":{
"embedding_model_path":"/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-f16.gguf",
"vectorstore_persist_directory": "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map1",
"embedding_framework_type":"llama_cpp_embedding",
"n_ctx": 2048,
"n_threads": 4,
"n_gpu_layers":20,
"verbose":0,
"chunk_size": 500,
"chunk_overlap": 50
}
}

View File

@@ -0,0 +1,24 @@
{
"models_server":
{
"model_inference_framework_type":"llama_cpp",
"multi_modal":1,
"chat_handler": "Qwen25VLChatHandler",
"vlm_model_path":"/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen2.5-VL-3B-Instruct-GGUF/Qwen2.5-VL-3B-Instruct-Q8_0.gguf",
"mmproj_model_path":"/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen2.5-VL-3B-Instruct-GGUF/mmproj-model-f16.gguf",
"n_ctx":30720,
"n_threads":4,
"n_gpu_layers":40,
"n_batch":24,
"n_ubatch":24,
"verbose":0,
"model_server_host":"localhost",
"model_server_port":8000,
"model_controller_workers":1
},
"models_client":
{
"server_url":"http://localhost:8000"
}
}

View File

@@ -0,0 +1,32 @@
{
"rag_mag":{
"embedding_model_path":"/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-f16.gguf",
"vectorstore_persist_directory": "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/memory/knowledge_base/map/vector_store/osm_map1",
"embedding_framework_type":"llamacpp_embedding",
"collection_name":"osm_map_docs",
"model_config_llamacpp":
{
"n_ctx":512,
"n_threads":4,
"n_gpu_layers":36,
"n_seq_max":256,
"flash_attn":1,
"verbose":0
},
"model_config_huggingFace":{
"model_kwargs": {
"device": "cuda"
},
"encode_kwargs":{
"normalize_embeddings": 1
}
},
"text_splitter":{
"chunk_size":500,
"chunk_overlap":50,
"separators":["\n\n", "\n", " ", ""]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,326 @@
{"text": "在地图上有一个名为 '学生宿舍10幢' 的地点或区域它的building是'dormitory'、name:zh是'学生宿舍10幢',其中心位置坐标大约在 (32.118363, 118.951407)。"}
{"text": "在地图上有一个名为 '基础实验楼' 的地点或区域它的building是'university'、building:levels是'5'、layer是'1',其中心位置坐标大约在 (32.112578, 118.952441)。"}
{"text": "在地图上有一个名为 '学生第九餐厅、清真餐厅、教工二餐厅' 的地点或区域它的amenity是'restaurant'、building是'yes'、cuisine是'chinese'、diet:halal是'yes'、fast_food是'cafeteria'、name:zh是'学生第九餐厅、清真餐厅、教工二餐厅',其中心位置坐标大约在 (32.118835, 118.950338)。"}
{"text": "在地图上有一个名为 '基础实验楼' 的地点或区域它的building是'university'、building:levels是'5'、layer是'1',其中心位置坐标大约在 (32.112291, 118.951332)。"}
{"text": "在地图上有一个名为 '学生宿舍9幢' 的地点或区域它的building是'dormitory'、name:zh是'学生宿舍9幢',其中心位置坐标大约在 (32.117856, 118.950905)。"}
{"text": "在地图上有一个名为 '学生宿舍6幢' 的地点或区域它的building是'dormitory'、name:zh是'学生宿舍6幢',其中心位置坐标大约在 (32.117406, 118.952443)。"}
{"text": "在地图上有一个名为 '信息化建设管理服务中心' 的地点或区域它的building是'yes'、building:levels是'3'、name:en是'Information Technology Service Center',其中心位置坐标大约在 (32.117418, 118.955253)。"}
{"text": "在地图上有一个名为 '学生宿舍11幢' 的地点或区域它的building是'dormitory'、name:zh是'学生宿舍11幢',其中心位置坐标大约在 (32.119133, 118.950956)。"}
{"text": "在地图上有一个名为 '南京大学档案馆' 的地点或区域它的building是'yes'、name:zh是'南京大学档案馆',其中心位置坐标大约在 (32.117318, 118.954447)。"}
{"text": "在地图上有一个名为 '学生宿舍8幢' 的地点或区域它的building是'dormitory'、name:zh是'学生宿舍8幢',其中心位置坐标大约在 (32.117844, 118.951860)。"}
{"text": "在地图上有一个名为 '校史馆(沈小平楼)' 的地点或区域它的building是'university'、name:zh是'校史馆(沈小平楼)',其中心位置坐标大约在 (32.116735, 118.953156)。"}
{"text": "在地图上有一个名为 '学生第四、五、六餐厅' 的地点或区域它的amenity是'restaurant'、building是'yes'、building:levels是'3'、cuisine是'chinese'、fast_food是'cafeteria'、internet_access是'wlan'、name:zh是'学生第四、五、六餐厅',其中心位置坐标大约在 (32.113385, 118.950076)。"}
{"text": "在地图上有一个名为 '敬文学生活动中心' 的地点或区域它的building是'yes'、name:zh是'敬文学生活动中心',其中心位置坐标大约在 (32.116008, 118.952685)。"}
{"text": "在地图上有一个名为 '学生宿舍7幢' 的地点或区域它的building是'dormitory'、name:zh是'学生宿舍7幢',其中心位置坐标大约在 (32.116881, 118.951416)。"}
{"text": "在地图上有一个名为 '扬州楼(行政北楼)' 的地点或区域它的building是'yes'、building:levels是'7'、name:zh是'扬州楼(行政北楼)',其中心位置坐标大约在 (32.114996, 118.957869)。"}
{"text": "在地图上有一个名为 '行政南楼' 的地点或区域它的building是'yes'、building:levels是'3'、name:zh是'行政南楼',其中心位置坐标大约在 (32.114177, 118.958016)。"}
{"text": "在地图上有一个名为 '南大仙林校区' 的地点或区域它的building是'transportation'、layer是'2',其中心位置坐标大约在 (32.110999, 118.954498)。"}
{"text": "在地图上有一个名为 '气象观测站' 的地点或区域它的building是'yes'、man_made是'monitoring_station'、monitoring:weather是'yes'、name:zh是'气象观测站',其中心位置坐标大约在 (32.120581, 118.952775)。"}
{"text": "在地图上有一个名为 '侨裕楼(外国语学院)' 的地点或区域它的building是'university'、name:zh是'侨裕楼(外国语学院)',其中心位置坐标大约在 (32.118473, 118.956012)。"}
{"text": "在地图上有一个名为 '游泳馆' 的地点或区域它的building是'yes'、leisure是'swimming_pool'、name:zh是'游泳馆'、sport是'swimming',其中心位置坐标大约在 (32.118386, 118.953233)。"}
{"text": "在地图上有一个名为 '社会学院' 的地点或区域它的addr:city是'南京市'、addr:housenumber是'163号'、addr:postcode是'210023'、addr:street是'仙林大道'、building是'university'、name:zh是'河仁楼(社会学院)',其中心位置坐标大约在 (32.120251, 118.954777)。"}
{"text": "在地图上有一个名为 '学生宿舍5幢' 的地点或区域它的building是'dormitory'、building:levels是'6'、name:zh是'学生宿舍5幢',其中心位置坐标大约在 (32.116960, 118.948511)。"}
{"text": "在地图上有一个名为 '学生宿舍3幢' 的地点或区域它的building是'dormitory'、building:levels是'6'、name:zh是'学生宿舍3幢',其中心位置坐标大约在 (32.115783, 118.949095)。"}
{"text": "在地图上有一个名为 '学生宿舍1幢' 的地点或区域它的building是'dormitory'、building:levels是'6'、name:zh是'学生宿舍1幢',其中心位置坐标大约在 (32.114598, 118.949687)。"}
{"text": "在地图上有一个名为 '逸夫楼' 的地点或区域它的building是'university'、building:levels是'5'、loc_name是'邵逸夫楼'、name:zh是'逸夫楼',其中心位置坐标大约在 (32.112757, 118.954903)。"}
{"text": "在地图上有一个名为 '国际学院' 的地点或区域它的building是'university'、building:levels是'5'、loc_name是'邵逸夫楼'、name:zh是'国际学院',其中心位置坐标大约在 (32.112661, 118.954128)。"}
{"text": "在地图上有一个名为 '左涤江楼' 的地点或区域它的building是'university'、building:levels是'5'、loc_name是'邵逸夫楼'、name:zh是'左涤江楼',其中心位置坐标大约在 (32.112855, 118.954377)。"}
{"text": "在地图上有一个名为 '大气科学学院' 的地点或区域它的building是'university'、building:levels是'5;4',其中心位置坐标大约在 (32.119925, 118.950090)。"}
{"text": "在地图上有一个名为 '学生第十食堂/教工餐厅' 的地点或区域它的amenity是'restaurant'、building是'yes'、cuisine是'chinese'、fast_food是'cafeteria'、name:zh是'学生第十食堂/教工餐厅',其中心位置坐标大约在 (32.117211, 118.957967)。"}
{"text": "在地图上有一个名为 '学生宿舍15幢' 的地点或区域它的building是'dormitory'、name:zh是'学生宿舍15幢',其中心位置坐标大约在 (32.117771, 118.958196)。"}
{"text": "在地图上有一个名为 '生命科学学院' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.121291, 118.949637)。"}
{"text": "在地图上有一个名为 '生命科学学院' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.121679, 118.950162)。"}
{"text": "在地图上有一个名为 '生命科学学院' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.121464, 118.950208)。"}
{"text": "在地图上有一个名为 '左涤江天文台' 的地点或区域它的building是'yes'、man_made是'observatory'、name:zh是'左涤江天文台',其中心位置坐标大约在 (32.124128, 118.955593)。"}
{"text": "在地图上有一个名为 '配电房' 的地点或区域它的building是'yes'、name:zh是'配电房',其中心位置坐标大约在 (32.121665, 118.958760)。"}
{"text": "在地图上有一个名为 '37栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.131308, 118.947252)。"}
{"text": "在地图上有一个名为 '38栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.130640, 118.947807)。"}
{"text": "在地图上有一个名为 '中合集团' 的地点或区域它的building是'yes'、name:zh是'中合集团',其中心位置坐标大约在 (32.130732, 118.957853)。"}
{"text": "在地图上有一个名为 '智慧园2' 的地点或区域它的building是'yes'、name:zh是'智慧园2',其中心位置坐标大约在 (32.129285, 118.958509)。"}
{"text": "在地图上有一个名为 '智慧园3' 的地点或区域它的building是'yes'、name:zh是'智慧园3',其中心位置坐标大约在 (32.129662, 118.958611)。"}
{"text": "在地图上有一个名为 '智慧园1' 的地点或区域它的building是'yes'、name:zh是'智慧园1',其中心位置坐标大约在 (32.128997, 118.958774)。"}
{"text": "在地图上有一个名为 '学生宿舍2幢' 的地点或区域它的building是'dormitory'、building:levels是'6'、name:zh是'学生宿舍2幢',其中心位置坐标大约在 (32.116117, 118.950136)。"}
{"text": "在地图上有一个名为 '学生宿舍4幢' 的地点或区域它的building是'dormitory'、building:levels是'6'、name:zh是'学生宿舍4幢',其中心位置坐标大约在 (32.117266, 118.949547)。"}
{"text": "在地图上有一个名为 '南京信息职业技术学院行政大楼' 的地点或区域它的building是'yes'、name:zh是'南京信息职业技术学院行政大楼',其中心位置坐标大约在 (32.127694, 118.937821)。"}
{"text": "在地图上有一个名为 '1号教学楼' 的地点或区域它的building是'yes'、layer是'1'、name:zh是'1号教学楼',其中心位置坐标大约在 (32.130094, 118.937824)。"}
{"text": "在地图上有一个名为 '2号教学楼' 的地点或区域它的building是'yes'、layer是'1'、name:zh是'2号教学楼',其中心位置坐标大约在 (32.130590, 118.937746)。"}
{"text": "在地图上有一个名为 '3号教学楼' 的地点或区域它的building是'yes'、layer是'1'、name:zh是'3号教学楼',其中心位置坐标大约在 (32.131123, 118.937560)。"}
{"text": "在地图上有一个名为 '阶教室1~4' 的地点或区域它的building是'yes'、name:zh是'阶教室1~4',其中心位置坐标大约在 (32.130217, 118.938816)。"}
{"text": "在地图上有一个名为 '阶教室5~8' 的地点或区域它的building是'yes'、name:zh是'阶教室5~8',其中心位置坐标大约在 (32.130733, 118.938732)。"}
{"text": "在地图上有一个名为 '阶教室9~10' 的地点或区域它的building是'yes'、name:zh是'阶教室9~10',其中心位置坐标大约在 (32.131223, 118.938468)。"}
{"text": "在地图上有一个名为 '南大国际会议中心' 的地点或区域它的building是'yes'、tourism是'hotel',其中心位置坐标大约在 (32.116389, 118.957899)。"}
{"text": "在地图上有一个名为 '仙林校区第二体育场附属体育馆' 的地点或区域它的building是'yes'、name:zh是'仙林校区第二体育场附属体育馆',其中心位置坐标大约在 (32.123331, 118.946338)。"}
{"text": "在地图上有一个名为 '学生第十一餐厅' 的地点或区域它的amenity是'restaurant'、building是'yes'、fast_food是'cafeteria'、name:zh是'学生第十一餐厅',其中心位置坐标大约在 (32.124886, 118.946992)。"}
{"text": "在地图上有一个名为 '学生公寓18幢' 的地点或区域它的building是'dormitory'、name:zh是'学生公寓18幢',其中心位置坐标大约在 (32.126459, 118.946337)。"}
{"text": "在地图上有一个名为 '学生公寓21幢/招待所/留学生公寓' 的地点或区域它的building是'dormitory'、name:zh是'学生公寓21幢/招待所/留学生公寓',其中心位置坐标大约在 (32.126662, 118.948693)。"}
{"text": "在地图上有一个名为 '学生公寓16幢' 的地点或区域它的building是'university'、name:zh是'学生公寓16幢',其中心位置坐标大约在 (32.124798, 118.946197)。"}
{"text": "在地图上有一个名为 '学生公寓20幢' 的地点或区域它的building是'dormitory'、name:zh是'学生公寓20幢',其中心位置坐标大约在 (32.126612, 118.947908)。"}
{"text": "在地图上有一个名为 '学生公寓19幢' 的地点或区域它的building是'dormitory'、name:zh是'学生公寓19幢',其中心位置坐标大约在 (32.126517, 118.947121)。"}
{"text": "在地图上有一个名为 '学生公寓17幢' 的地点或区域它的building是'university'、name:zh是'学生公寓17幢',其中心位置坐标大约在 (32.125641, 118.946295)。"}
{"text": "在地图上有一个名为 '历史学院' 的地点或区域它的addr:city是'南京市'、addr:housenumber是'163号'、addr:postcode是'210023'、addr:street是'仙林大道'、building是'university'、name:zh是'历史学院',其中心位置坐标大约在 (32.121063, 118.954117)。"}
{"text": "在地图上有一个名为 '匡亚明学院' 的地点或区域它的building是'university'、name:zh是'匡亚明学院',其中心位置坐标大约在 (32.118904, 118.955208)。"}
{"text": "在地图上有一个名为 '信息管理学院' 的地点或区域它的building是'university'、name:zh是'信息管理学院',其中心位置坐标大约在 (32.119130, 118.955489)。"}
{"text": "在地图上有一个名为 '南京大学美术馆' 的地点或区域它的addr:city是'南京'、addr:housenumber是'163'、addr:postcode是'210023'、addr:street是'仙林大道'、building是'yes'、name:zh是'南京大学美术馆',其中心位置坐标大约在 (32.116135, 118.957631)。"}
{"text": "在地图上有一个名为 '教学楼' 的地点或区域它的addr:city是'南京'、addr:street是'仙林大道'、building是'school'、name:zh是'教学楼',其中心位置坐标大约在 (32.111407, 118.961720)。"}
{"text": "在地图上有一个名为 '教学楼' 的地点或区域它的building是'school'、name:zh是'教学楼',其中心位置坐标大约在 (32.111990, 118.962025)。"}
{"text": "在地图上有一个名为 '学生宿舍25幢' 的地点或区域它的building是'dormitory'、building:levels是'5'、name:zh是'学生宿舍25幢',其中心位置坐标大约在 (32.127235, 118.954093)。"}
{"text": "在地图上有一个名为 '天文与空间科学学院' 的地点或区域它的building是'university'、building:levels是'5'、name:zh是'天文与空间科学学院',其中心位置坐标大约在 (32.127596, 118.954874)。"}
{"text": "在地图上有一个名为 '南京大学量子材料微结构研究中心' 的地点或区域它的building是'university'、building:levels是'1',其中心位置坐标大约在 (32.127804, 118.954043)。"}
{"text": "在地图上有一个名为 '39幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.109930, 118.951469)。"}
{"text": "在地图上有一个名为 '40幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.109632, 118.951555)。"}
{"text": "在地图上有一个名为 '37幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.110030, 118.952233)。"}
{"text": "在地图上有一个名为 '38幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.109744, 118.952319)。"}
{"text": "在地图上有一个名为 '30幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.110134, 118.953036)。"}
{"text": "在地图上有一个名为 '31幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.109840, 118.953103)。"}
{"text": "在地图上有一个名为 '41幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.109254, 118.951746)。"}
{"text": "在地图上有一个名为 '42幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.108965, 118.951820)。"}
{"text": "在地图上有一个名为 '43幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.108625, 118.951928)。"}
{"text": "在地图上有一个名为 '44幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.108319, 118.952017)。"}
{"text": "在地图上有一个名为 '45幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.107998, 118.952113)。"}
{"text": "在地图上有一个名为 '32幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.109502, 118.953026)。"}
{"text": "在地图上有一个名为 '33幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.109186, 118.953071)。"}
{"text": "在地图上有一个名为 '34幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.108885, 118.953131)。"}
{"text": "在地图上有一个名为 '35幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.108578, 118.953183)。"}
{"text": "在地图上有一个名为 '36幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.108245, 118.953227)。"}
{"text": "在地图上有一个名为 '46幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.107483, 118.952259)。"}
{"text": "在地图上有一个名为 '47幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.107174, 118.952342)。"}
{"text": "在地图上有一个名为 '48幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.106871, 118.952440)。"}
{"text": "在地图上有一个名为 '53幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.107842, 118.953462)。"}
{"text": "在地图上有一个名为 '54幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.107507, 118.953356)。"}
{"text": "在地图上有一个名为 '55幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.107182, 118.953363)。"}
{"text": "在地图上有一个名为 '56幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.106892, 118.953424)。"}
{"text": "在地图上有一个名为 '62幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.107907, 118.954543)。"}
{"text": "在地图上有一个名为 '63幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.107589, 118.954466)。"}
{"text": "在地图上有一个名为 '64幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.107264, 118.954429)。"}
{"text": "在地图上有一个名为 '65幢' 的地点或区域它的building是'residential'、building:levels是'6',其中心位置坐标大约在 (32.106970, 118.954427)。"}
{"text": "在地图上有一个名为 '垃圾场' 的地点或区域它的building是'yes'、name:zh是'垃圾场',其中心位置坐标大约在 (32.118340, 118.957592)。"}
{"text": "在地图上有一个名为 '25幢' 的地点或区域它的building是'apartments'、building:levels是'6'、name:zh是'25幢',其中心位置坐标大约在 (32.109631, 118.954057)。"}
{"text": "在地图上有一个名为 '24幢' 的地点或区域它的building是'apartments'、building:levels是'6'、name:zh是'24幢',其中心位置坐标大约在 (32.109899, 118.953932)。"}
{"text": "在地图上有一个名为 '环境学院' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.119491, 118.948427)。"}
{"text": "在地图上有一个名为 '环境学院' 的地点或区域它的building是'university'、layer是'1',其中心位置坐标大约在 (32.119185, 118.948005)。"}
{"text": "在地图上有一个名为 '基础实验楼' 的地点或区域它的building是'university'、building:levels是'5',其中心位置坐标大约在 (32.112310, 118.950955)。"}
{"text": "在地图上有一个名为 '基础实验楼' 的地点或区域它的building是'university'、building:levels是'5',其中心位置坐标大约在 (32.112523, 118.952460)。"}
{"text": "在地图上有一个名为 '基础实验楼' 的地点或区域它的building是'university'、building:levels是'5',其中心位置坐标大约在 (32.112429, 118.951419)。"}
{"text": "在地图上有一个名为 '基础实验楼' 的地点或区域它的building是'university'、building:levels是'5',其中心位置坐标大约在 (32.112593, 118.952946)。"}
{"text": "在地图上有一个名为 '基础实验楼' 的地点或区域它的building是'university'、building:levels是'5'、layer是'1',其中心位置坐标大约在 (32.112491, 118.951825)。"}
{"text": "在地图上有一个名为 '基础实验楼' 的地点或区域它的building是'university'、building:levels是'5',其中心位置坐标大约在 (32.112494, 118.952244)。"}
{"text": "在地图上有一个名为 '花房' 的地点或区域它的building是'university'、name:zh是'花房',其中心位置坐标大约在 (32.118788, 118.948634)。"}
{"text": "在地图上有一个名为 '大气科学学院' 的地点或区域它的building是'university'、building:levels是'5;4',其中心位置坐标大约在 (32.120083, 118.950190)。"}
{"text": "在地图上有一个名为 '化学化工学院' 的地点或区域它的building是'university'、building:levels是'6'、building:min_level是'1'、layer是'1',其中心位置坐标大约在 (32.121157, 118.947274)。"}
{"text": "在地图上有一个名为 '化学化工学院' 的地点或区域它的building是'university'、building:levels是'6'、building:min_level是'1'、layer是'1',其中心位置坐标大约在 (32.120775, 118.947408)。"}
{"text": "在地图上有一个名为 '化学化工学院' 的地点或区域它的building是'university'、building:levels是'6'、building:min_level是'1'、layer是'1',其中心位置坐标大约在 (32.120376, 118.947546)。"}
{"text": "在地图上有一个名为 '化学化工学院' 的地点或区域它的building是'university'、building:levels是'6',其中心位置坐标大约在 (32.120444, 118.946806)。"}
{"text": "在地图上有一个名为 '化学化工学院' 的地点或区域它的building是'university'、building:levels是'6',其中心位置坐标大约在 (32.120901, 118.948341)。"}
{"text": "在地图上有一个名为 '化学化工学院' 的地点或区域它的building是'university'、building:levels是'6',其中心位置坐标大约在 (32.120601, 118.948172)。"}
{"text": "在地图上有一个名为 '化学化工学院' 的地点或区域它的building是'university'、building:levels是'6',其中心位置坐标大约在 (32.121236, 118.947934)。"}
{"text": "在地图上有一个名为 '化学化工学院' 的地点或区域它的building是'university'、building:levels是'6'、layer是'1',其中心位置坐标大约在 (32.120922, 118.948087)。"}
{"text": "在地图上有一个名为 '26幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109311, 118.954077)。"}
{"text": "在地图上有一个名为 '27幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.108977, 118.954186)。"}
{"text": "在地图上有一个名为 '28幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.108684, 118.954248)。"}
{"text": "在地图上有一个名为 '29幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.108380, 118.954421)。"}
{"text": "在地图上有一个名为 '15幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.110100, 118.955418)。"}
{"text": "在地图上有一个名为 '16幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109806, 118.955426)。"}
{"text": "在地图上有一个名为 '17幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109490, 118.955344)。"}
{"text": "在地图上有一个名为 '18幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109173, 118.955389)。"}
{"text": "在地图上有一个名为 '19幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.108864, 118.955484)。"}
{"text": "在地图上有一个名为 '20幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.108538, 118.955531)。"}
{"text": "在地图上有一个名为 '21幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.108277, 118.955617)。"}
{"text": "在地图上有一个名为 '22幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109996, 118.954613)。"}
{"text": "在地图上有一个名为 '23幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109712, 118.954772)。"}
{"text": "在地图上有一个名为 '菜根谭快递站' 的地点或区域它的amenity是'post_office'、building是'yes',其中心位置坐标大约在 (32.116479, 118.951733)。"}
{"text": "在地图上有一个名为 '6栋快递自提柜' 的地点或区域它的amenity是'post_office'、building是'yes',其中心位置坐标大约在 (32.116825, 118.952329)。"}
{"text": "在地图上有一个名为 '15栋快递自提柜' 的地点或区域它的amenity是'post_office'、building是'yes',其中心位置坐标大约在 (32.117748, 118.957996)。"}
{"text": "在地图上有一个名为 '勇园快递站' 的地点或区域它的amenity是'post_office'、building是'yes',其中心位置坐标大约在 (32.125444, 118.946840)。"}
{"text": "在地图上有一个名为 '南京和光智能制造研究院' 的地点或区域它的building是'commercial',其中心位置坐标大约在 (32.128399, 118.947911)。"}
{"text": "在地图上有一个名为 '淳朴亭' 的地点或区域它的amenity是'shelter'、building是'yes',其中心位置坐标大约在 (32.120761, 118.950780)。"}
{"text": "在地图上有一个名为 '1号楼' 的地点或区域它的amenity是'restaurant'、building是'yes'、layer是'1',其中心位置坐标大约在 (32.125778, 118.958005)。"}
{"text": "在地图上有一个名为 '4号楼' 的地点或区域它的building是'yes'、office是'research',其中心位置坐标大约在 (32.125877, 118.958793)。"}
{"text": "在地图上有一个名为 '2号楼' 的地点或区域它的building是'yes'、office是'research',其中心位置坐标大约在 (32.126742, 118.958709)。"}
{"text": "在地图上有一个名为 '3号楼' 的地点或区域它的building是'yes'、office是'research',其中心位置坐标大约在 (32.126479, 118.958768)。"}
{"text": "在地图上有一个名为 '四组团十一食堂站' 的地点或区域它的amenity是'shelter'、building是'yes'、shelter_type是'public_transport',其中心位置坐标大约在 (32.125395, 118.947557)。"}
{"text": "在地图上有一个名为 '68幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.107854, 118.955680)。"}
{"text": "在地图上有一个名为 '69幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.107528, 118.955660)。"}
{"text": "在地图上有一个名为 '70幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.107215, 118.955609)。"}
{"text": "在地图上有一个名为 '71幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.106868, 118.955563)。"}
{"text": "在地图上有一个名为 '8幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.110285, 118.956830)。"}
{"text": "在地图上有一个名为 '9幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109995, 118.957069)。"}
{"text": "在地图上有一个名为 '10幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109699, 118.957142)。"}
{"text": "在地图上有一个名为 '11幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109385, 118.957204)。"}
{"text": "在地图上有一个名为 '12幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109062, 118.957116)。"}
{"text": "在地图上有一个名为 '社区中心' 的地点或区域它的building是'yes'、building:levels是'2',其中心位置坐标大约在 (32.107709, 118.956429)。"}
{"text": "在地图上有一个名为 '74幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.108124, 118.957410)。"}
{"text": "在地图上有一个名为 '80幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.108339, 118.957947)。"}
{"text": "在地图上有一个名为 '75幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.107766, 118.957429)。"}
{"text": "在地图上有一个名为 '81幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.108037, 118.958140)。"}
{"text": "在地图上有一个名为 '76幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.107430, 118.957603)。"}
{"text": "在地图上有一个名为 '82幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.107778, 118.958480)。"}
{"text": "在地图上有一个名为 '77幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.107061, 118.957794)。"}
{"text": "在地图上有一个名为 '83幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.107447, 118.958692)。"}
{"text": "在地图上有一个名为 '78幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.106817, 118.958220)。"}
{"text": "在地图上有一个名为 '84幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.107141, 118.958941)。"}
{"text": "在地图上有一个名为 '85幢' 的地点或区域它的building是'apartments'、building:levels是'7',其中心位置坐标大约在 (32.106847, 118.959144)。"}
{"text": "在地图上有一个名为 '6幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.110385, 118.957643)。"}
{"text": "在地图上有一个名为 '7幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.110093, 118.957839)。"}
{"text": "在地图上有一个名为 '5幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109132, 118.958036)。"}
{"text": "在地图上有一个名为 '4幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109522, 118.958366)。"}
{"text": "在地图上有一个名为 '3幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.109844, 118.958394)。"}
{"text": "在地图上有一个名为 '2幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.110169, 118.958529)。"}
{"text": "在地图上有一个名为 '1幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.110483, 118.958469)。"}
{"text": "在地图上有一个名为 '169幢' 的地点或区域它的building是'retail'、building:levels是'3',其中心位置坐标大约在 (32.110268, 118.954063)。"}
{"text": "在地图上有一个名为 '168幢' 的地点或区域它的building是'retail',其中心位置坐标大约在 (32.110328, 118.955335)。"}
{"text": "在地图上有一个名为 '166幢' 的地点或区域它的building是'retail'、building:levels是'3',其中心位置坐标大约在 (32.110523, 118.955614)。"}
{"text": "在地图上有一个名为 '167幢' 的地点或区域它的building是'retail'、building:levels是'3',其中心位置坐标大约在 (32.110430, 118.954565)。"}
{"text": "在地图上有一个名为 '165幢' 的地点或区域它的building是'retail'、building:levels是'3',其中心位置坐标大约在 (32.110555, 118.957025)。"}
{"text": "在地图上有一个名为 '162幢' 的地点或区域它的building是'commercial'、building:levels是'3',其中心位置坐标大约在 (32.110836, 118.958390)。"}
{"text": "在地图上有一个名为 '163幢' 的地点或区域它的building是'retail'、building:levels是'3',其中心位置坐标大约在 (32.110824, 118.957713)。"}
{"text": "在地图上有一个名为 '164幢' 的地点或区域它的building是'retail'、building:levels是'3',其中心位置坐标大约在 (32.110672, 118.956623)。"}
{"text": "在地图上有一个名为 '校图书馆' 的地点或区域它的amenity是'library'、building是'yes'、layer是'1',其中心位置坐标大约在 (32.122664, 118.939190)。"}
{"text": "在地图上有一个名为 '创客空间' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.122582, 118.938378)。"}
{"text": "在地图上有一个名为 '乐业楼' 的地点或区域它的building是'university'、building:levels是'5',其中心位置坐标大约在 (32.122773, 118.938103)。"}
{"text": "在地图上有一个名为 '敬业楼' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.121950, 118.938149)。"}
{"text": "在地图上有一个名为 '大学生文艺活动室' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.122076, 118.938397)。"}
{"text": "在地图上有一个名为 '求真楼' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.121952, 118.939201)。"}
{"text": "在地图上有一个名为 '乐群楼' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.120856, 118.937737)。"}
{"text": "在地图上有一个名为 '国际教育学院' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.120584, 118.938605)。"}
{"text": "在地图上有一个名为 '躬行楼' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.125922, 118.938780)。"}
{"text": "在地图上有一个名为 '捷豹路虎南京卓越培训中心' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.125456, 118.938323)。"}
{"text": "在地图上有一个名为 '国际贸易与物流管理研究所' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.124787, 118.938531)。"}
{"text": "在地图上有一个名为 '北京精雕科技集团有限公司' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.125507, 118.938939)。"}
{"text": "在地图上有一个名为 '南京市第十三国家职业技能鉴定所' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.125120, 118.938871)。"}
{"text": "在地图上有一个名为 'NYG-1918' 的地点或区域它的aeroway是'hangar'、building是'hangar',其中心位置坐标大约在 (32.125464, 118.937265)。"}
{"text": "在地图上有一个名为 '行政楼' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.119834, 118.936977)。"}
{"text": "在地图上有一个名为 '南京工业职业技术大学(北门)' 的地点或区域它的amenity是'shelter'、building是'yes'、layer是'1',其中心位置坐标大约在 (32.126320, 118.937323)。"}
{"text": "在地图上有一个名为 '南京工业职业技术大学(东门)' 的地点或区域它的amenity是'shelter'、building是'yes'、layer是'1',其中心位置坐标大约在 (32.124452, 118.940097)。"}
{"text": "在地图上有一个名为 '大玻璃教室' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.120889, 118.938660)。"}
{"text": "在地图上有一个名为 '科技园' 的地点或区域它的building是'yes'、name:zh是'科技园',其中心位置坐标大约在 (32.127799, 118.938988)。"}
{"text": "在地图上有一个名为 '科技园' 的地点或区域它的building是'yes'、name:zh是'科技园',其中心位置坐标大约在 (32.127746, 118.939689)。"}
{"text": "在地图上有一个名为 '科技园' 的地点或区域它的building是'yes'、name:zh是'科技园',其中心位置坐标大约在 (32.128082, 118.939604)。"}
{"text": "在地图上有一个名为 '科技园' 的地点或区域它的building是'yes'、name:zh是'科技园',其中心位置坐标大约在 (32.127996, 118.939891)。"}
{"text": "在地图上有一个名为 '科技园' 的地点或区域它的building是'yes'、name:zh是'科技园',其中心位置坐标大约在 (32.129711, 118.939745)。"}
{"text": "在地图上有一个名为 '国际关系学院' 的地点或区域它的building是'university',其中心位置坐标大约在 (32.121706, 118.951702)。"}
{"text": "在地图上有一个名为 '18栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132807, 118.959391)。"}
{"text": "在地图上有一个名为 '南大和园农贸市场' 的地点或区域它的building是'retail',其中心位置坐标大约在 (32.110969, 118.959158)。"}
{"text": "在地图上有一个名为 '14幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.108454, 118.957242)。"}
{"text": "在地图上有一个名为 '垃圾站' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.109919, 118.956197)。"}
{"text": "在地图上有一个名为 '羊山书苑' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.116108, 118.938897)。"}
{"text": "在地图上有一个名为 '166幢' 的地点或区域它的building是'retail'、building:levels是'3',其中心位置坐标大约在 (32.110489, 118.955112)。"}
{"text": "在地图上有一个名为 '164幢' 的地点或区域它的building是'retail'、building:levels是'3',其中心位置坐标大约在 (32.110733, 118.957095)。"}
{"text": "在地图上有一个名为 '13幢' 的地点或区域它的building是'apartments'、building:levels是'6',其中心位置坐标大约在 (32.108746, 118.957172)。"}
{"text": "在地图上有一个名为 '栖霞区核酸小屋(废弃)' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.111018, 118.956190)。"}
{"text": "在地图上有一个名为 'B4栋' 的地点或区域它的addr:place是'江苏生命科技创新园'、building是'yes',其中心位置坐标大约在 (32.132991, 118.951775)。"}
{"text": "在地图上有一个名为 'B6栋' 的地点或区域它的addr:place是'江苏生命科技创新园'、building是'yes',其中心位置坐标大约在 (32.132506, 118.951993)。"}
{"text": "在地图上有一个名为 '17栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132902, 118.959949)。"}
{"text": "在地图上有一个名为 'A1栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.133079, 118.956082)。"}
{"text": "在地图上有一个名为 '协同创新大厦' 的地点或区域它的building是'commercial',其中心位置坐标大约在 (32.127895, 118.958680)。"}
{"text": "在地图上有一个名为 '中国游戏数码港' 的地点或区域它的building是'university'、name:zh是'中国游戏数码港',其中心位置坐标大约在 (32.130547, 118.954394)。"}
{"text": "在地图上有一个名为 '智慧园6' 的地点或区域它的building是'university'、name:zh是'智慧园6',其中心位置坐标大约在 (32.130347, 118.955872)。"}
{"text": "在地图上有一个名为 '7栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.130157, 118.955108)。"}
{"text": "在地图上有一个名为 '1号楼' 的地点或区域它的building是'commercial',其中心位置坐标大约在 (32.129982, 118.953671)。"}
{"text": "在地图上有一个名为 'B7栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132289, 118.951108)。"}
{"text": "在地图上有一个名为 'B5栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132869, 118.952425)。"}
{"text": "在地图上有一个名为 'B3栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132862, 118.952796)。"}
{"text": "在地图上有一个名为 '5栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128561, 118.963282)。"}
{"text": "在地图上有一个名为 '4栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128913, 118.963328)。"}
{"text": "在地图上有一个名为 '1栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129287, 118.963404)。"}
{"text": "在地图上有一个名为 '2栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129487, 118.962760)。"}
{"text": "在地图上有一个名为 '3栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129674, 118.962109)。"}
{"text": "在地图上有一个名为 '12栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129635, 118.960945)。"}
{"text": "在地图上有一个名为 '15栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129482, 118.960264)。"}
{"text": "在地图上有一个名为 '17栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128992, 118.960374)。"}
{"text": "在地图上有一个名为 '16栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128719, 118.961012)。"}
{"text": "在地图上有一个名为 '18栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128271, 118.961030)。"}
{"text": "在地图上有一个名为 '19栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128476, 118.960509)。"}
{"text": "在地图上有一个名为 '2号楼' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129411, 118.952702)。"}
{"text": "在地图上有一个名为 '4栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129165, 118.964417)。"}
{"text": "在地图上有一个名为 '江苏省体育科学研究所' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.121071, 118.965970)。"}
{"text": "在地图上有一个名为 '7栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120339, 118.961607)。"}
{"text": "在地图上有一个名为 '14栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.121731, 118.961821)。"}
{"text": "在地图上有一个名为 '13栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.121459, 118.961716)。"}
{"text": "在地图上有一个名为 '12栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.121172, 118.961455)。"}
{"text": "在地图上有一个名为 '5栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120905, 118.959973)。"}
{"text": "在地图上有一个名为 '6栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120919, 118.960480)。"}
{"text": "在地图上有一个名为 '10栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120890, 118.961643)。"}
{"text": "在地图上有一个名为 '11栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120895, 118.961136)。"}
{"text": "在地图上有一个名为 '8栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120623, 118.961565)。"}
{"text": "在地图上有一个名为 '9栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120621, 118.961001)。"}
{"text": "在地图上有一个名为 '4栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120641, 118.960413)。"}
{"text": "在地图上有一个名为 '1栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120370, 118.960301)。"}
{"text": "在地图上有一个名为 '3栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120642, 118.959976)。"}
{"text": "在地图上有一个名为 '2栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.120378, 118.959871)。"}
{"text": "在地图上有一个名为 '社会学院' 的地点或区域它的addr:city是'南京市'、addr:housenumber是'163号'、addr:postcode是'210023'、addr:street是'仙林大道'、building是'university'、name:zh是'河仁楼(社会学院)',其中心位置坐标大约在 (32.120253, 118.955138)。"}
{"text": "在地图上有一个名为 '社会学院' 的地点或区域它的addr:city是'南京市'、addr:housenumber是'163号'、addr:postcode是'210023'、addr:street是'仙林大道'、building是'university'、name:zh是'河仁楼(社会学院)',其中心位置坐标大约在 (32.120383, 118.954906)。"}
{"text": "在地图上有一个名为 '社会学院' 的地点或区域它的addr:city是'南京市'、addr:housenumber是'163号'、addr:postcode是'210023'、addr:street是'仙林大道'、building是'university'、name:zh是'河仁楼(社会学院)',其中心位置坐标大约在 (32.120413, 118.954960)。"}
{"text": "在地图上有一个名为 'C栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.119948, 118.960706)。"}
{"text": "在地图上有一个名为 'B栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.119634, 118.960664)。"}
{"text": "在地图上有一个名为 'A栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.119320, 118.960599)。"}
{"text": "在地图上有一个名为 '64栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.125317, 118.969390)。"}
{"text": "在地图上有一个名为 '67栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.126039, 118.969808)。"}
{"text": "在地图上有一个名为 '66栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.125863, 118.968927)。"}
{"text": "在地图上有一个名为 '19栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.126518, 118.966927)。"}
{"text": "在地图上有一个名为 '19栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.126366, 118.966595)。"}
{"text": "在地图上有一个名为 '16栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.126936, 118.966471)。"}
{"text": "在地图上有一个名为 '16栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127019, 118.966836)。"}
{"text": "在地图上有一个名为 '13栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127508, 118.966792)。"}
{"text": "在地图上有一个名为 '13栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127494, 118.966445)。"}
{"text": "在地图上有一个名为 '21栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.125988, 118.965525)。"}
{"text": "在地图上有一个名为 '21栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.125873, 118.965161)。"}
{"text": "在地图上有一个名为 '20栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.126193, 118.966009)。"}
{"text": "在地图上有一个名为 '18栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.126449, 118.965650)。"}
{"text": "在地图上有一个名为 '17栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.126884, 118.965660)。"}
{"text": "在地图上有一个名为 '17栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.126884, 118.965212)。"}
{"text": "在地图上有一个名为 '14栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127321, 118.965718)。"}
{"text": "在地图上有一个名为 '14栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127322, 118.965270)。"}
{"text": "在地图上有一个名为 '11栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127760, 118.965718)。"}
{"text": "在地图上有一个名为 '11栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127761, 118.965270)。"}
{"text": "在地图上有一个名为 '15栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127162, 118.964452)。"}
{"text": "在地图上有一个名为 '12栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127669, 118.964467)。"}
{"text": "在地图上有一个名为 '8栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128144, 118.966811)。"}
{"text": "在地图上有一个名为 '5栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128669, 118.966843)。"}
{"text": "在地图上有一个名为 '10栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128170, 118.964445)。"}
{"text": "在地图上有一个名为 '9栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128200, 118.965509)。"}
{"text": "在地图上有一个名为 '6栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128684, 118.965523)。"}
{"text": "在地图上有一个名为 '7栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128662, 118.964463)。"}
{"text": "在地图上有一个名为 '1栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129204, 118.966742)。"}
{"text": "在地图上有一个名为 '2栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129179, 118.965975)。"}
{"text": "在地图上有一个名为 '3栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129175, 118.965204)。"}
{"text": "在地图上有一个名为 '63栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.124969, 118.970335)。"}
{"text": "在地图上有一个名为 '62栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.124820, 118.969650)。"}
{"text": "在地图上有一个名为 '61栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.124294, 118.970244)。"}
{"text": "在地图上有一个名为 '60栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.123897, 118.970512)。"}
{"text": "在地图上有一个名为 '65栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.125580, 118.970582)。"}
{"text": "在地图上有一个名为 '17栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129717, 118.970176)。"}
{"text": "在地图上有一个名为 '16栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129524, 118.969207)。"}
{"text": "在地图上有一个名为 '15栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129399, 118.968380)。"}
{"text": "在地图上有一个名为 '9栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128441, 118.969340)。"}
{"text": "在地图上有一个名为 '6栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127960, 118.969911)。"}
{"text": "在地图上有一个名为 '2栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127368, 118.969415)。"}
{"text": "在地图上有一个名为 '1栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127087, 118.968454)。"}
{"text": "在地图上有一个名为 '5栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.127656, 118.968453)。"}
{"text": "在地图上有一个名为 '8栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128310, 118.968501)。"}
{"text": "在地图上有一个名为 '12栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.128893, 118.968532)。"}
{"text": "在地图上有一个名为 '13栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.129073, 118.969856)。"}
{"text": "在地图上有一个名为 '3栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.130874, 118.970144)。"}
{"text": "在地图上有一个名为 '2栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.130780, 118.969386)。"}
{"text": "在地图上有一个名为 '1栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.130813, 118.968619)。"}
{"text": "在地图上有一个名为 '13栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.131817, 118.969006)。"}
{"text": "在地图上有一个名为 '14栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.131887, 118.969861)。"}
{"text": "在地图上有一个名为 '22栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132974, 118.969870)。"}
{"text": "在地图上有一个名为 '18栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132380, 118.969319)。"}
{"text": "在地图上有一个名为 '19栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132455, 118.970049)。"}
{"text": "在地图上有一个名为 '9栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.131374, 118.970134)。"}
{"text": "在地图上有一个名为 '8栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.131265, 118.969410)。"}
{"text": "在地图上有一个名为 '7栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.131298, 118.968695)。"}
{"text": "在地图上有一个名为 '10栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.133020, 118.966464)。"}
{"text": "在地图上有一个名为 '7栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132756, 118.966139)。"}
{"text": "在地图上有一个名为 '4栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132447, 118.965873)。"}
{"text": "在地图上有一个名为 '3栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132080, 118.966671)。"}
{"text": "在地图上有一个名为 '1栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.131734, 118.966638)。"}
{"text": "在地图上有一个名为 '2栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.131682, 118.967276)。"}
{"text": "在地图上有一个名为 '5栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132085, 118.967455)。"}
{"text": "在地图上有一个名为 '8栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132447, 118.967655)。"}
{"text": "在地图上有一个名为 '11栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132833, 118.967891)。"}
{"text": "在地图上有一个名为 '12栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.133128, 118.967260)。"}
{"text": "在地图上有一个名为 '9栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132782, 118.967042)。"}
{"text": "在地图上有一个名为 '6栋' 的地点或区域它的building是'yes',其中心位置坐标大约在 (32.132436, 118.966831)。"}
{"text": "在地图上有一个名为 '仙林自行车场' 的地点或区域它的leisure是'sports_centre'、name:zh是'仙林自行车场'、sport是'cycling',其中心位置坐标大约在 (32.117653, 118.960528)。"}
{"text": "在地图上有一个名为 '1号楼' 的地点或区域它的building是'commercial',其中心位置坐标大约在 (32.129982, 118.953671)。"}

View File

@@ -0,0 +1,318 @@
{"text": "仿真环境中有一个名为 'wall' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [0.0, 49.0, 2.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'wall_2' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [0.0, -49.0, 2.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'wall_3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [49.0, 0.0, 2.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'wall_4' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-49.0, 0.0, 2.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'law_office_82' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [8.47, 9.356, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'thrift_shop_83' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [1.35, 9.356, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'salon_84' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [15.7, 9.356, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'oak_tree_85' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-6.418, 10.37, 0.0, 0.0, -0.0, 0.55]。"}
{"text": "仿真环境中有一个名为 'apartment_86' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [1.573, 27.565, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'osrf_first_office_87' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [27.003, 20.15, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'pine_tree_88' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [16.096, 34.82, 0.0, 0.0, -0.0, 2.24546]。"}
{"text": "仿真环境中有一个名为 'pine_tree_89' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [18.7, 29.33, 0.0, 0.0, -0.0, 0.605226]。"}
{"text": "仿真环境中有一个名为 'pine_tree_90' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [22.56, 34.58, 0.0, 0.0, -0.0, 0.34397]。"}
{"text": "仿真环境中有一个名为 'oak_tree_91' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [31.49, 35.89, 0.0, 0.0, 0.0, -3.03]。"}
{"text": "仿真环境中有一个名为 'postbox_92' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [24.0, 4.33, 0.15, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'fast_food_93' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [28.25, -13.93, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'dumpster_94' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [15.94, -13.63, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'fire_hydrant_95' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [38.5, -4.38, 0.15, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'house_2_125' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [1.61, -14.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'house_2_126' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-0.59, -26.6, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'pine_tree_127' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -37.0, 0.0, 0.0, -0.0, 0.124132]。"}
{"text": "仿真环境中有一个名为 'pine_tree_128' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-2.0, -37.0, 0.0, 0.0, -0.0, 0.143519]。"}
{"text": "仿真环境中有一个名为 'pine_tree_129' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [3.0, -37.0, 0.0, 0.0, -0.0, 1.74324]。"}
{"text": "仿真环境中有一个名为 'pine_tree_130' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [8.0, -37.0, 0.0, 0.0, -0.0, 2.04822]。"}
{"text": "仿真环境中有一个名为 'pine_tree_131' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [13.0, -37.0, 0.0, 0.0, -0.0, 0.035642]。"}
{"text": "仿真环境中有一个名为 'pine_tree_132' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -32.0, 0.0, 0.0, -0.0, 2.69167]。"}
{"text": "仿真环境中有一个名为 'pine_tree_133' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -27.0, 0.0, 0.0, -0.0, 1.45432]。"}
{"text": "仿真环境中有一个名为 'pine_tree_134' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -22.0, 0.0, 0.0, -0.0, 2.10283]。"}
{"text": "仿真环境中有一个名为 'pine_tree_135' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -17.0, 0.0, 0.0, -0.0, 1.33207]。"}
{"text": "仿真环境中有一个名为 'pine_tree_136' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -12.0, 0.0, 0.0, -0.0, 2.93245]。"}
{"text": "仿真环境中有一个名为 'pine_tree_137' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -7.0, 0.0, 0.0, -0.0, 0.63871]。"}
{"text": "仿真环境中有一个名为 'pine_tree_138' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-2.0, -8.0, 0.0, 0.0, -0.0, 0.416833]。"}
{"text": "仿真环境中有一个名为 'pine_tree_139' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [3.0, -8.0, 0.0, 0.0, -0.0, 1.84106]。"}
{"text": "仿真环境中有一个名为 'pine_tree_140' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [8.0, -8.0, 0.0, 0.0, -0.0, 2.21107]。"}
{"text": "仿真环境中有一个名为 'pine_tree_141' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [13.0, -8.0, 0.0, 0.0, -0.0, 0.145852]。"}
{"text": "仿真环境中有一个名为 'pine_tree_142' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [18.0, -8.0, 0.0, 0.0, -0.0, 2.31415]。"}
{"text": "仿真环境中有一个名为 'post_office_143' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-26.686, 11.119, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'postbox_144' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-28.58, 4.33, 0.15, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'pine_tree_145' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-30.709, 7.15, 0.0, 0.0, -0.0, 2.2424]。"}
{"text": "仿真环境中有一个名为 'house_1_146' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-27.49, 25.344, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'pickup_147' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-36.72, 21.02, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'oak_tree_148' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.811, 11.78, 0.0, 0.0, -0.0, 0.55]。"}
{"text": "仿真环境中有一个名为 'pine_tree_149' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-22.97, 15.87, 0.0, 0.0, -0.0, 1.82]。"}
{"text": "仿真环境中有一个名为 'oak_tree_150' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-36.78, 36.47, 0.0, 0.0, -0.0, 1.7]。"}
{"text": "仿真环境中有一个名为 'pine_tree_151' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-23.94, 34.92, 0.0, 0.0, -0.0, 0.56]。"}
{"text": "仿真环境中有一个名为 'cardboard_box_152' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-32.0, 8.0, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'salon_154' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-27.65, -9.35, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'law_office_155' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-34.886, -9.408, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'house_3_156' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-33.39, -21.3, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'house_3_157' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-28.126, -32.93, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'house_3_158' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.739, -19.38, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'pine_tree_159' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-37.48, -13.67, 0.0, 0.0, -0.0, 2.99694]。"}
{"text": "仿真环境中有一个名为 'pine_tree_160' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-22.26, -7.39, 0.0, 0.0, -0.0, 0.002426]。"}
{"text": "仿真环境中有一个名为 'pine_tree_161' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-22.26, -10.46, 0.0, 0.0, -0.0, 0.646469]。"}
{"text": "仿真环境中有一个名为 'pine_tree_162' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.045, -28.058, 0.0, 0.0, -0.0, 2.2678]。"}
{"text": "仿真环境中有一个名为 'pine_tree_163' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-37.48, -36.76, 0.0, 0.0, -0.0, 1.67752]。"}
{"text": "仿真环境中有一个名为 'pine_tree_164' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-33.06, -37.17, 0.0, 0.0, -0.0, 0.584309]。"}
{"text": "仿真环境中有一个名为 'pine_tree_165' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.34, -29.166, 0.0, 0.0, -0.0, 2.40377]。"}
{"text": "仿真环境中有一个名为 'fire_hydrant_166' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-20.34, -4.38, 0.15, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_169' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_170' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_171' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_172' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.7, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_174' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-48.7, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_175' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_176' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_177' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_178' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_180' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-48.7, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_181' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'stop_sign_183' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, -3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'stop_sign_184' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -41.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'stop_sign_185' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, 3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'stop_sign_186' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_sign_187' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'lamp_post_189' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.1, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'lamp_post_190' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.2, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'lamp_post_191' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-3.0, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'lamp_post_192' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [9.0, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'lamp_post_193' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [21.0, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'lamp_post_194' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [27.0, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_199' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_200' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_201' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_202' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_203' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_204' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_205' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.7, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_206' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.7, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_207' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_208' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_211' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, 14.85, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_212' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_213' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, 14.85, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_214' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_215' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, 14.85, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_216' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_218' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.7, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_219' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, 14.85, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_220' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_223' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.1, -41.3, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_224' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.2, -41.3, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_225' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.1, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_226' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.2, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_227' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.1, -3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_228' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.2, -3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_229' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [4.8, -41.3, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_230' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [24.6, -41.3, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_231' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [4.8, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_232' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [24.6, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_233' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [4.8, -3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_234' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [24.6, -3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'hatchback_blue_241' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-17.55, -32.5226, 0.0, 0.0, -0.0, 1.5708]。"}
{"text": "仿真环境中有一个名为 'pickup_242' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-12.45, -20.8953, 0.0, 0.0, -0.0, 1.5708]。"}
{"text": "仿真环境中有一个名为 'suv_243' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [42.45, -34.7406, 0.0, 0.0, 0.0, -1.5708]。"}
{"text": "仿真环境中有一个名为 'pickup_244' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [47.55, -27.5423, 0.0, 0.0, 0.0, -1.5708]。"}
{"text": "仿真环境中有一个名为 'hatchback_blue_245' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-17.55, 32.931, 0.0, 0.0, -0.0, 1.5708]。"}
{"text": "仿真环境中有一个名为 'pickup_246' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-12.45, 30.6875, 0.0, 0.0, -0.0, 1.5708]。"}
{"text": "仿真环境中有一个名为 'hatchback_247' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [42.45, 28.0591, 0.0, 0.0, 0.0, -1.5708]。"}
{"text": "仿真环境中有一个名为 'pickup_249' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-24.9957, -2.55, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'hatchback_250' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-21.6052, 2.55, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'suv_251' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [21.4393, -2.55, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'hatchback_blue_252' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [7.08835, 2.55, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'apartment_86' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [1.573, 27.565, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'cardboard_box_152' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-32.0, 8.0, 0.149, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'dumpster_94' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [15.94, -13.63, 0.000918, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'fast_food_93' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [28.25, -13.93, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'fire_hydrant_166' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-20.34, -4.38, 0.15, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'fire_hydrant_95' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [38.5, -4.38, 0.15, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'ground_plane_0' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [0.0, 0.0, -0.1, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'hatchback_247' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [42.45, 28.0591, 0.0, 0.0, 0.0, -1.5708]。"}
{"text": "仿真环境中有一个名为 'hatchback_250' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-21.6052, 2.55, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'hatchback_blue_241' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-17.55, -32.5226, 0.0, 0.0, -0.0, 1.5708]。"}
{"text": "仿真环境中有一个名为 'hatchback_blue_245' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-17.55, 32.931, 0.0, 0.0, -0.0, 1.5708]。"}
{"text": "仿真环境中有一个名为 'hatchback_blue_252' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [7.08835, 2.55, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'house_1_146' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-27.49, 25.344, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'house_2_125' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [1.61, -14.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'house_2_125_clone' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [14.3003, -27.3963, 0.0, 0.0, -0.0, 3.14159]。"}
{"text": "仿真环境中有一个名为 'house_2_125_clone_clone' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [28.4365, -27.4764, 0.0, 0.0, -0.0, 3.14159]。"}
{"text": "仿真环境中有一个名为 'house_2_126' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-0.59, -26.6, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'house_3_156' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-33.39, -21.3, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'house_3_157' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-28.126, -32.93, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'house_3_158' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.739, -19.38, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'lamp_post_189' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.1, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'lamp_post_190' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.2, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'lamp_post_191' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-3.0, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'lamp_post_192' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [9.0, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'lamp_post_193' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [21.0, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'lamp_post_194' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [27.0, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'law_office_155' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-34.886, -9.408, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'law_office_82' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [8.47, 9.356, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'oak_tree_148' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.811, 11.78, 0.0, 0.0, -0.0, 0.55]。"}
{"text": "仿真环境中有一个名为 'oak_tree_150' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-36.78, 36.47, 0.0, 0.0, -0.0, 1.7]。"}
{"text": "仿真环境中有一个名为 'oak_tree_85' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-6.418, 10.37, 0.0, 0.0, -0.0, 0.55]。"}
{"text": "仿真环境中有一个名为 'oak_tree_91' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [31.49, 35.89, 0.0, 0.0, 0.0, -3.03]。"}
{"text": "仿真环境中有一个名为 'osrf_first_office_87' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [27.003, 20.15, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'pickup_147' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-36.72, 21.02, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'pickup_242' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-12.45, -20.8953, 0.0, 0.0, -0.0, 1.5708]。"}
{"text": "仿真环境中有一个名为 'pickup_244' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [47.55, -27.5423, 0.0, 0.0, 0.0, -1.5708]。"}
{"text": "仿真环境中有一个名为 'pickup_246' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-12.45, 30.6875, 0.0, 0.0, -0.0, 1.5708]。"}
{"text": "仿真环境中有一个名为 'pickup_249' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-24.9957, -2.55, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'pine_tree_127' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -37.0, 0.0, 0.0, -0.0, 0.124132]。"}
{"text": "仿真环境中有一个名为 'pine_tree_128' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-2.0, -37.0, 0.0, 0.0, -0.0, 0.143519]。"}
{"text": "仿真环境中有一个名为 'pine_tree_129' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [3.0, -37.0, 0.0, 0.0, -0.0, 1.74324]。"}
{"text": "仿真环境中有一个名为 'pine_tree_130' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [8.0, -37.0, 0.0, 0.0, -0.0, 2.04822]。"}
{"text": "仿真环境中有一个名为 'pine_tree_131' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [13.0, -37.0, 0.0, 0.0, -0.0, 0.035642]。"}
{"text": "仿真环境中有一个名为 'pine_tree_132' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -32.0, 0.0, 0.0, -0.0, 2.69167]。"}
{"text": "仿真环境中有一个名为 'pine_tree_133' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -27.0, 0.0, 0.0, -0.0, 1.45432]。"}
{"text": "仿真环境中有一个名为 'pine_tree_134' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -22.0, 0.0, 0.0, -0.0, 2.10283]。"}
{"text": "仿真环境中有一个名为 'pine_tree_135' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -17.0, 0.0, 0.0, -0.0, 1.33207]。"}
{"text": "仿真环境中有一个名为 'pine_tree_136' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -12.0, 0.0, 0.0, -0.0, 2.93245]。"}
{"text": "仿真环境中有一个名为 'pine_tree_137' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-7.0, -7.0, 0.0, 0.0, -0.0, 0.63871]。"}
{"text": "仿真环境中有一个名为 'pine_tree_138' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-2.0, -8.0, 0.0, 0.0, -0.0, 0.416833]。"}
{"text": "仿真环境中有一个名为 'pine_tree_139' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [3.0, -8.0, 0.0, 0.0, -0.0, 1.84106]。"}
{"text": "仿真环境中有一个名为 'pine_tree_140' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [8.0, -8.0, 0.0, 0.0, -0.0, 2.21107]。"}
{"text": "仿真环境中有一个名为 'pine_tree_141' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [13.0, -8.0, 0.0, 0.0, -0.0, 0.145852]。"}
{"text": "仿真环境中有一个名为 'pine_tree_142' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [18.0, -8.0, 0.0, 0.0, -0.0, 2.31415]。"}
{"text": "仿真环境中有一个名为 'pine_tree_145' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-30.709, 7.15, 0.0, 0.0, -0.0, 2.2424]。"}
{"text": "仿真环境中有一个名为 'pine_tree_149' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-22.97, 15.87, 0.0, 0.0, -0.0, 1.82]。"}
{"text": "仿真环境中有一个名为 'pine_tree_151' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-23.94, 34.92, 0.0, 0.0, -0.0, 0.56]。"}
{"text": "仿真环境中有一个名为 'pine_tree_159' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-37.48, -13.67, 0.0, 0.0, -0.0, 2.99694]。"}
{"text": "仿真环境中有一个名为 'pine_tree_160' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-22.26, -7.39, 0.0, 0.0, -0.0, 0.002426]。"}
{"text": "仿真环境中有一个名为 'pine_tree_161' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-22.26, -10.46, 0.0, 0.0, -0.0, 0.646469]。"}
{"text": "仿真环境中有一个名为 'pine_tree_162' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.045, -28.058, 0.0, 0.0, -0.0, 2.2678]。"}
{"text": "仿真环境中有一个名为 'pine_tree_163' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-37.48, -36.76, 0.0, 0.0, -0.0, 1.67752]。"}
{"text": "仿真环境中有一个名为 'pine_tree_164' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-33.06, -37.17, 0.0, 0.0, -0.0, 0.584309]。"}
{"text": "仿真环境中有一个名为 'pine_tree_165' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.34, -29.166, 0.0, 0.0, -0.0, 2.40377]。"}
{"text": "仿真环境中有一个名为 'pine_tree_88' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [16.096, 34.82, 0.0, 0.0, -0.0, 2.24546]。"}
{"text": "仿真环境中有一个名为 'pine_tree_89' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [18.7, 29.33, 0.0, 0.0, -0.0, 0.605226]。"}
{"text": "仿真环境中有一个名为 'pine_tree_90' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [22.56, 34.58, 0.0, 0.0, -0.0, 0.34397]。"}
{"text": "仿真环境中有一个名为 'post_office_143' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-26.686, 11.119, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'postbox_144' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-28.58, 4.33, 0.15, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'postbox_92' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [24.0, 4.33, 0.15, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'salon_154' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-27.65, -9.35, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'salon_84' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [15.7, 9.356, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_169' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_170' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_171' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_172' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.7, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_174' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-48.7, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_175' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_176' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_177' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_178' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, 3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_180' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-48.7, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'stop_light_post_181' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'stop_sign_183' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, -3.7, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'stop_sign_184' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -41.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'stop_sign_185' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, 3.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'stop_sign_186' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'stop_sign_187' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -3.7, 0.0, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'suv_243' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [42.45, -34.7406, 0.0, 0.0, 0.0, -1.5708]。"}
{"text": "仿真环境中有一个名为 'suv_251' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [21.4393, -2.55, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_199' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_200' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_201' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_202' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_203' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_204' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_205' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.7, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_206' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.7, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_206_clone' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.7, 14.85, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_207' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -30.15, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_208' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, -15.3, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_211' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, 14.85, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_212' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-41.3, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_213' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, 14.85, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_214' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-11.3, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_215' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, 14.85, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_216' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-18.7, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_218' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.7, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_219' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, 14.85, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_220' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [41.3, 29.7, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_223' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.1, -41.3, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_224' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.2, -41.3, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_225' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.1, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_226' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.2, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_227' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-35.1, -3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_228' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.2, -3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_229' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [4.8, -41.3, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_230' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [24.6, -41.3, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_231' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [4.8, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_232' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [24.6, 3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_233' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [4.8, -3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_234' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [24.6, -3.7, 0.0, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'text_model1' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-28.626, 29.3431, 1.5636, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_0' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.5396, 9.27041, 1.52314, 0.0, -0.0, 3.1384]。"}
{"text": "仿真环境中有一个名为 'text_model1_1' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-34.6937, -19.4703, 1.23472, 0.0, -0.0, 1.58393]。"}
{"text": "仿真环境中有一个名为 'text_model1_10' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [0.85316, -27.5651, 1.06281, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'text_model1_11' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [13.1838, -29.0718, 1.3, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model1_12' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [27.2798, -29.1869, 1.3, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model1_2' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-24.4421, -21.2078, 1.23942, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'text_model1_3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-29.9588, -34.22, 1.21346, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model1_4' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-34.8868, -8.05648, 1.65897, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_5' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [9.49647, 25.4469, 1.93202, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'text_model1_6' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-0.959037, 7.52929, 2.3312, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model1_7' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [24.485, 8.07813, 1.9831, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'text_model1_8' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [28.6711, -17.7386, 2.25038, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model1_9' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [0.6481, -15.5815, 1.21912, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model2' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-24.9409, 28.6794, 1.57869, 0.0, 0.0, -1.52716]。"}
{"text": "仿真环境中有一个名为 'text_model2_0' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-32.0989, -20.9209, 1.20285, 0.0, 0.0, -1.56366]。"}
{"text": "仿真环境中有一个名为 'text_model2_1' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-27.0336, -19.7574, 1.20887, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'text_model2_10' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [13.4714, -25.2341, 1.3, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_11' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [27.6367, -25.3145, 1.3, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_2' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-28.5112, -31.6317, 1.21235, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.3234, -7.52915, 1.96408, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_4' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-6.48868, 24.506, 1.93221, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'text_model2_5' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [8.51104, 7.97486, 1.5687, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model2_6' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [29.5159, 8.05862, 1.68793, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'text_model2_7' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [28.6861, -10.0987, 1.62563, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_8' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [1.18864, -12.1442, 1.04206, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_9' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-2.59305, -27.0309, 1.08359, 0.0, -0.0, 1.57]。"}
{"text": "仿真环境中有一个名为 'text_model3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [16.0786, -29.8724, 1.3, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model3_0' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [30.5156, -30.1889, 1.3, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model3_3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [13.3752, 7.56599, 2.29163, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model3_4' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [3.82571, -16.4045, 0.823551, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'text_model3_5' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [1.66214, -24.3491, 0.831783, 0.0, 0.0, -1.57]。"}
{"text": "仿真环境中有一个名为 'thrift_shop_83' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [1.35, 9.356, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'wall' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [0.0, 49.0, 2.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'wall_2' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [0.0, -49.0, 2.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'wall_3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [49.0, 0.0, 2.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'wall_4' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-49.0, 0.0, 2.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-31.827, 31.5085, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-25.6311, 30.6182, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_0' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-26.8003, 8.35325, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_1' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-37.6617, -20.0872, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_0' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-31.2117, -23.79, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_2' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-23.4712, -20.896, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_1' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-27.3657, -20.3372, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-29.8503, -35.7088, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_2' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-28.8359, -31.3351, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_4' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-34.1638, -5.49078, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-28.8844, -5.77655, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_5' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [12.5719, 24.0672, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_4' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-9.74093, 24.1795, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_6' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-0.52561, 3.27106, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_5' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [8.06137, 6.29462, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_7' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [21.4701, 4.0873, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_6' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [31.9781, 5.86941, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_8' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [27.8526, -31.0723, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_7' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [31.517, -2.96729, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_9' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [0.448999, -17.1905, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_8' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [1.08878, -11.5143, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_10' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [11.1354, -29.1096, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_9' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-4.35234, -25.1903, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model3_3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [14.0061, 1.63948, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model3_4' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [4.2153, -19.7779, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model3_5' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [3.52886, -24.2947, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'ground_plane_0' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [-10.2583, 20.1204, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'telephone_pole_206_clone' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [48.6722, 16.6841, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'house_2_125_clone' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [11.3102, -26.491, 0.0, 0.0, -0.0, 3.14]。"}
{"text": "仿真环境中有一个名为 'house_2_125_clone_clone' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [28.4365, -27.4764, 0.0, 0.0, -0.0, 3.14159]。"}
{"text": "仿真环境中有一个名为 'text_model1_11' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [13.1838, -29.0718, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model3' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [16.0786, -29.8724, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_10' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [13.4714, -25.2341, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model2_11' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [27.6367, -25.3145, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model1_12' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [27.2798, -29.1869, 0.0, 0.0, -0.0, 0.0]。"}
{"text": "仿真环境中有一个名为 'text_model3_0' 的物体,其位置和姿态(x, y, z, roll, pitch, yaw)为: [30.5156, -30.1889, 0.0, 0.0, -0.0, 0.0]。"}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python3
import os
import sys
import io
from typing import List, Dict, Any, Optional, Tuple
from pydantic import BaseModel
# 请求模型
class TextInferenceRequest(BaseModel):
user_prompt: str
max_tokens: int = 200
temperature: float = 0.7
top_p: float = 0.95
system_prompt: Optional[str] = None
stop: Optional[List[str]] = None
class MultimodalInferenceRequest(BaseModel):
user_prompt: str
image_data: List[str] # base64编码的图像数据列表
max_tokens: int = 300
temperature: float = 0.7
top_p: float = 0.95
system_prompt: Optional[str] = None
stop: Optional[List[str]] = None
class InferenceRequest(BaseModel):
user_prompt: str
image_data: Optional[List[str]]=None # base64编码的图像数据列表
max_tokens: int = 300
temperature: float = 0.7
top_p: float = 0.95
system_prompt: Optional[str] = None
stop: Optional[List[str]] = None
# 响应模型定义
class InferenceResponse(BaseModel):
result: str
metadata: Dict[str, Any]
class HealthResponse(BaseModel):
status: str
model_loaded: bool
process_id: int
load_time: float
model_path: str

View File

@@ -0,0 +1,429 @@
#!/usr/bin/env python3
import os
import sys
import io
from typing import List, Dict, Any, Optional, Tuple
from pathlib import Path
import time
# from langchain.agents import initialize_agent, AgentType
# from langchain.tools import BaseTool, Tool
# from langchain.llms import LlamaCpp
# from langchain.callbacks.manager import CallbackManager
# from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
# from langchain.prompts import PromptTemplate
# from langchain.chains import LLMChain
# import magic # 用于识别文件类型(辅助文件搜索工具)
# from langchain.vectorstores import Chroma
# from langchain.embeddings import LlamaCppEmbeddings
# from langchain.text_splitter import RecursiveCharacterTextSplitter
# from langchain.document_loaders import (
# TextLoader, UnstructuredFileLoader, PDFMinerLoader,
# UnstructuredMarkdownLoader, Docx2txtLoader
# )
# from langchain.chains import RetrievalQA
# from langchain.memory import ConversationBufferWindowMemory
# from langchain.prompts import PromptTemplate
# from langchain.schema import HumanMessage, AIMessage
import logging
# import argparse
# import base64
# from fastapi import FastAPI, HTTPException, APIRouter, Path
# from fastapi.responses import JSONResponse
# from fastapi_utils.cbv import cbv
# from fastapi_utils.inferring_router import InferringRouter
from pydantic import BaseModel
from PIL import Image
import numpy as np
import requests
import json
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
# from tools.core.common_functions import read_json_file
from tools.data_process.image_process.image_process import PIL_image_to_base64
from models.model_definition import (TextInferenceRequest,MultimodalInferenceRequest,InferenceRequest,InferenceResponse,HealthResponse)
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
#定义模型Client端
class Models_Client:
def __init__(self, server_url: str = "http://localhost:8000"):
"""
初始化多模态客户端
参数:
server_url: 服务器地址,格式为 "http://host:port"
"""
self.server_url = server_url
self.text_endpoint = f"{server_url}/text/inference"
self.multimodal_endpoint = f"{server_url}/multimodal/inference"
self.model_inference_endpoint = f"{server_url}/model/inference"
self.health_endpoint = f"{server_url}/model/health"
def wait_for_server(self, timeout: int = 300, check_interval: int = 5) -> bool:
"""
等待服务器加载完成
参数:
timeout: 最大等待时间(秒)
check_interval: 检查间隔(秒)
返回:
服务器是否准备就绪
"""
start_time = time.time()
while time.time() - start_time < timeout:
try:
health = self.check_health()
if health.get("status") == "healthy":
print("服务器已准备就绪")
return True
print(f"服务器正在加载中,已等待 {int(time.time() - start_time)} 秒...")
time.sleep(check_interval)
except Exception as e:
print(f"检查服务器状态失败: {str(e)}")
time.sleep(check_interval)
print(f"超时: 服务器在 {timeout} 秒内未准备就绪")
return False
def check_health(self) -> Dict[str, Any]:
"""检查服务器健康状态"""
try:
response = requests.get(self.health_endpoint)
response.raise_for_status()
return response.json()
except Exception as e:
return {"status": "error", "message": str(e)}
def text_inference(self, request:TextInferenceRequest) -> Dict[str, Any]:
"""
发送纯文本推理请求
参数:
prompt: 提示文本
max_tokens: 最大生成token数
temperature: 生成温度,控制随机性
top_p: 核采样参数
stop: 停止序列
返回:
包含推理结果的字典
"""
# payload = {
# "user_prompt": prompt,
# "max_tokens": max_tokens,
# "temperature": temperature,
# "top_p": top_p,
# "system_prompt":system_prompt,
# "stop": stop
# }
try:
response = requests.post(
self.text_endpoint,
headers={"Content-Type": "application/json"},
data=json.dumps(request)
)
response.raise_for_status()
return response.json()
except Exception as e:
return {"error": str(e)}
def text_inference2(self,
prompt: str,
max_tokens: int = 200,
temperature: float = 0.7,
top_p: float = 0.95,
system_prompt: str="",
stop: Optional[List[str]] = None) -> Dict[str, Any]:
"""
发送纯文本推理请求
参数:
prompt: 提示文本
max_tokens: 最大生成token数
temperature: 生成温度,控制随机性
top_p: 核采样参数
stop: 停止序列
返回:
包含推理结果的字典
"""
payload = {
"user_prompt": prompt,
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p,
"system_prompt":system_prompt,
"stop": stop
}
try:
response = requests.post(
self.text_endpoint,
headers={"Content-Type": "application/json"},
data=json.dumps(payload)
)
response.raise_for_status()
return response.json()
except Exception as e:
return {"error": str(e)}
def multimodal_inference(self, request:MultimodalInferenceRequest) -> Dict[str, Any]:
"""
发送多模态推理请求(文本+图像)
参数:
prompt: 提示文本
image_paths: 图像文件路径列表
images: jpeg base64对象列表
max_tokens: 最大生成token数
temperature: 生成温度,控制随机性
top_p: 核采样参数
stop: 停止序列
返回:
包含推理结果的字典
"""
# # 处理图像
# image_data = []
# if images:
# # for img in images:
# try:
# # image_data.append(PIL_image_to_base64(image=img))
# image_data= images
# except Exception as e:
# return {"error": f"处理图像base64对象失败: {str(e)}"}
# elif image_paths:
# for path in image_paths:
# try:
# image_data.append(PIL_image_to_base64(image_path=path))
# except Exception as e:
# return {"error": f"处理图像 {path} 失败: {str(e)}"}
# if not image_data:
# return {"error": "未提供有效的图像数据"}
# payload = {
# "user_prompt": prompt,
# "image_data": image_data,
# "max_tokens": max_tokens,
# "temperature": temperature,
# "top_p": top_p,
# "system_prompt": system_prompt,
# "stop": stop
# }
try:
response = requests.post(
url=self.multimodal_endpoint,
headers={"Content-Type": "application/json"},
data=json.dumps(request)
)
response.raise_for_status()
return response.json()
except Exception as e:
return {"error": str(e)}
def multimodal_inference2(self,
prompt: str,
image_paths: Optional[List[str]] = None,
images: Optional[List[str]] = None,
max_tokens: int = 300,
temperature: float = 0.7,
top_p: float = 0.95,
system_prompt: str="",
stop: Optional[List[str]] = None) -> Dict[str, Any]:
"""
发送多模态推理请求(文本+图像)
参数:
prompt: 提示文本
image_paths: 图像文件路径列表
images: jpeg base64对象列表
max_tokens: 最大生成token数
temperature: 生成温度,控制随机性
top_p: 核采样参数
stop: 停止序列
返回:
包含推理结果的字典
"""
# 处理图像
image_data = []
if images:
# for img in images:
try:
# image_data.append(PIL_image_to_base64(image=img))
image_data= images
except Exception as e:
return {"error": f"处理图像base64对象失败: {str(e)}"}
elif image_paths:
for path in image_paths:
try:
image_data.append(PIL_image_to_base64(image_path=path))
except Exception as e:
return {"error": f"处理图像 {path} 失败: {str(e)}"}
if not image_data:
return {"error": "未提供有效的图像数据"}
payload = {
"user_prompt": prompt,
"image_data": image_data,
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p,
"system_prompt": system_prompt,
"stop": stop
}
try:
response = requests.post(
url=self.multimodal_endpoint,
headers={"Content-Type": "application/json"},
data=json.dumps(payload)
)
response.raise_for_status()
return response.json()
except Exception as e:
return {"error": str(e)}
def model_inference(self,
prompt: str,
image_paths: Optional[List[str]] = None,
images: Optional[List[str]] = None,
max_tokens: int = 300,
temperature: float = 0.7,
top_p: float = 0.95,
system_prompt: str="",
stop: Optional[List[str]] = None) -> Dict[str, Any]:
"""
发送多模态推理请求(文本+图像)
参数:
prompt: 提示文本
image_paths: 图像文件路径列表
images: jpeg base64对象列表
max_tokens: 最大生成token数
temperature: 生成温度,控制随机性
top_p: 核采样参数
stop: 停止序列
返回:
包含推理结果的字典
"""
# 处理图像
image_data = []
if images:
# for img in images:
try:
# image_data.append(PIL_image_to_base64(image=img))
image_data= images
except Exception as e:
return {"error": f"处理图像base64对象失败: {str(e)}"}
elif image_paths:
for path in image_paths:
try:
image_data.append(PIL_image_to_base64(image_path=path))
except Exception as e:
return {"error": f"处理图像 {path} 失败: {str(e)}"}
payload = {
"prompt": prompt,
"image_data": None,
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p,
"system_prompt": system_prompt,
"stop": stop
}
#如果有image_data就加入
if image_data:
payload["image_data"] = image_data
logger.info(f"payload: {payload}")
try:
response = requests.post(
self.model_inference_endpoint,
headers={"Content-Type": "application/json"},
data=json.dumps(payload)
)
response.raise_for_status()
return response.json()
except Exception as e:
return {"error": str(e)}
def model_client_test(inference_case=1):
config_json_file="/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/models/model_config.json"
from tools.core.common_functions import read_json_file
model_client_config = read_json_file(config_json_file)["models_client"][0]
model_client = Models_Client(model_client_config["server_url"])
if 2==inference_case:
test_prompt = "检测图中的蓝色气球,并按照固定的结构(<|object_ref_start|>蓝色气球<|object_ref_end|><|box_start|>(xmin,ymin),(xamx,ymax)<|box_end|>)给出2D框坐标"
test_image_path = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/datas/test1/balloon2.jpg"
reponse = model_client.multimodal_inference(test_prompt,image_paths=[test_image_path])
elif 1==inference_case:
test_prompt = "请用中文对图片进行场景描述!"
test_image_path = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/datas/test1/fj1.jpg"
reponse = model_client.multimodal_inference(test_prompt,image_paths=[test_image_path])
elif 0==inference_case:
test_prompt = "请你说一下《西游记》中孙悟空的性格特点!"
reponse = model_client.text_inference(test_prompt)
print("reponse: ",reponse)
def main():
model_client_test(inference_case=2)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,301 @@
#!/usr/bin/env python3
import os
import sys
import io
from typing import List, Dict, Any, Optional, Tuple
import multiprocessing
import logging
from pathlib import Path
import time
from curses.panel import top_panel
from email import message
from cv2 import log
from pydantic import BaseModel
from fastapi import FastAPI, HTTPException, APIRouter, Depends
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi_utils.cbv import cbv
from fastapi_utils.inferring_router import InferringRouter
from sympy import content
import uvicorn
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
from tools.core.common_functions import read_json_file
from models_server import Models_Server, TextInferenceRequest, MultimodalInferenceRequest, InferenceRequest
from models_server import _process_instance,_process_initialized
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
shared_state = multiprocessing.Manager().dict()
shared_state["model_loaded"] = False
shared_state["model_path"] = ""
shared_state["load_start_time"] = 0
shared_state["load_complete"] = False
# # 请求模型
# class TextInferenceRequest(BaseModel):
# user_prompt: str
# max_tokens: int = 200
# temperature: float = 0.7
# top_p: float = 0.95
# system_prompt: Optional[str] = None
# stop: Optional[List[str]] = None
# class MultimodalInferenceRequest(BaseModel):
# user_prompt: str
# image_data: List[str] # base64编码的图像数据列表
# max_tokens: int = 300
# temperature: float = 0.7
# top_p: float = 0.95
# system_prompt: Optional[str] = None
# stop: Optional[List[str]] = None
# class InferenceRequest(BaseModel):
# user_prompt: str
# image_data: Optional[List[str]]=None # base64编码的图像数据列表
# max_tokens: int = 300
# temperature: float = 0.7
# top_p: float = 0.95
# system_prompt: Optional[str] = None
# stop: Optional[List[str]] = None
# 响应模型定义
class InferenceResponse(BaseModel):
result: str
metadata: Dict[str, Any]
class HealthResponse(BaseModel):
status: str
model_loaded: bool
process_id: int
load_time: float
model_path: str
model_router = InferringRouter() # 支持自动推断参数类型
# 依赖注入:获取单例模型服务
def get_model_server() -> Models_Server:
"""依赖注入:获取当前进程的单例服务实例"""
logger.info(f"=== get_model_server 被触发Process ID: {os.getpid()} ===")
if _process_instance is None:
config_json_file= f"{project_root}/models/model_config.json"
print("config_json_file: ",config_json_file)
# global model_server_config
model_server_config = read_json_file(config_json_file)["models_server"]
if model_server_config is None:
logger.error("get_model_server: 配置未初始化!")
raise HTTPException(status_code=500, detail="服务配置未初始化")
# 获取单例实例
model_server = Models_Server(model_server_config)
if not model_server.model_loaded:
model_server._wait_for_model_load(model_server_config)
model_server.init_model(model_server_config["model_inference_framework_type"],
model_server_config)
# _process_initialized = True
else:
model_server = _process_instance
if not model_server.model_loaded:
model_server._wait_for_model_load(model_server_config)
model_server.init_model(model_server_config["model_inference_framework_type"],
model_server_config)
logger.info(f"get_model_server 返回实例状态: 加载={model_server.model_loaded}, Process ID={model_server.process_id}")
return model_server
@cbv(model_router) # 将类与路由绑定
class Models_Controller:
def __init__(self, model_server: Models_Server = Depends(get_model_server)):
self.model_server = model_server # 实例变量,确保每个请求可用
logger.info(f"Models_Controller init success! Process ID: {os.getpid()}")
"""
初始化成员变量
"""
#model: vlmllm
# model_server = Depends(get_model_server)
# self.model_loaded = False
# self.load_start_time = 0.0
# self.inference_start_time = 0.0
# #定义服务器地址和端口
# self.model_server_host = None
# self.model_server_port = None
# logger.info("Models_Controller init success!")
# def parse_config(self,config_path):
def set_model_server_host(self,model_server_host):
"""
设置模型推理服务器的地址
"""
try:
self.model_server_host = model_server_host
return True
except Exception as e:
logger.error(f"设置模型推理服务器的地址失败: {str(e)}")
return False
def set_model_server_port(self,model_server_port):
"""
设置模型推理服务器的端口
"""
try:
self.model_server_port = model_server_port
return True
except Exception as e:
logger.error(f"设置模型推理服务器的端口失败: {str(object=e)}")
return False
@model_router.post("/text/inference", response_model=Dict[str, Any])
async def text_inference(self, request: TextInferenceRequest):
"""纯文本推理端点"""
return self.model_server.text_inference(request=request)
@model_router.post("/multimodal/inference", response_model=Dict[str, Any])
async def multimodal_inference(self,request: MultimodalInferenceRequest):
"""大模型多模态推理(文本+图像)端点"""
return self.model_server.multimodal_inference(request=request)
@model_router.post("/model/inference", response_model=Dict[str, Any])
async def model_inference(self,request: InferenceRequest):
"""大模型(纯文本或多模态(文本+图像))推理端点"""
return self.model_server.model_inference(request=request)
# API端点
@model_router.get("/model/health", response_model=Dict[str, Any])
async def model_health_check(self):
"""检查大模型服务健康状态"""
return {
"status": "healthy" if shared_state["model_loaded"] else "loading",
"model_loaded": shared_state["model_loaded"],
"process_id": self.model_server.process_id,
"load_time": shared_state.get("load_time", 0),
"model_path": shared_state.get("model_path", "")
}
# def start_http_model_controller( model_server_host="localhost",model_server_port=8000,model_workers=1):
# """
# 初始化并启用FastAPI应用
# """
# # #初始化加载模型
# # self.init_model(config["model_inference_framework_type"],config)
# # 初始化FastAPI应用
# model_app = FastAPI(title="多模态大模型推理服务")
# # 配置CORS
# # model_app.add_middleware(
# # CORSMiddleware,
# # allow_origins=["*"],
# # allow_credentials=True,
# # allow_methods=["*"],
# # allow_headers=["*"],
# # )
# try:
# # 将路由注册到 app
# model_app.include_router(model_router)
#
# uvicorn.run(app=model_app, host=model_server_host, port=model_server_port,workers=model_workers)
# return True
# except Exception as e:
# logger.error(f"初始化并启用FastAPI应用失败: {str(e)}")
# return False
def create_app() -> FastAPI:
"""创建FastAPI应用"""
app = FastAPI(title="Multimodal API", version="1.0")
# 配置CORS
# app.add_middleware(
# CORSMiddleware,
# allow_origins=["*"],
# allow_credentials=True,
# allow_methods=["*"],
# allow_headers=["*"],
# )
# 注册路由
app.include_router(model_router)
return app
def main():
#model server 和client初始化
# models_server = Models_Server()
# from tools.core.common_functions import read_json_file
# "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/models/model_config.json"
print(f"project_root111:{project_root}")
config_json_file= f"{project_root}/models/model_config.json"
print("config_json_file: ",config_json_file)
# global model_server_config
model_server_config = read_json_file(config_json_file)["models_server"]
# while( not models_server.model_loaded ) :
# models_server.init_model(model_inference_framework_type=model_server_config["model_infer恩策_framework_type"],
# config=model_server_config)
# logger.info("model_loaded success!")
# start_http_model_controller(model_server_host=model_server_config["model_server_host"],
# model_server_port=model_server_config["model_server_port"])
# model_server_config= {
# "model_inference_framework_type":"llama_cpp",
# "multi_modal":1,
# "chat_handler": "Qwen25VLChatHandler",
# "vlm_model_path":"/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen2.5-VL-3B-Instruct-GGUF/Qwen2.5-VL-3B-Instruct-Q8_0.gguf",
# "mmproj_model_path":"/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen2.5-VL-3B-Instruct-GGUF/mmproj-model-f16.gguf",
# "n_ctx":30720,
# "n_threads":4,
# "n_gpu_layers":40,
# "n_batch":24,
# "verbose":1,
# "model_server_host":"localhost",
# "model_server_port":8000,
# "model_controller_workers":1
# }
# print("model_server_config: ",model_server_config)
uvicorn.run(
app="models_controller:create_app",
host=model_server_config["model_server_host"],
port=model_server_config["model_server_port"],
workers=model_server_config["model_controller_workers"],
factory=True,
log_level="info"
)
if __name__ == "__main__":
main()
# test_inference(0)
logger.info("work finish")

View File

@@ -0,0 +1,150 @@
#!/usr/bin/env python3
import os
import sys
import io
from typing import List, Dict, Any, Optional, Tuple
# from langchain.agents import initialize_agent, AgentType
# from langchain.tools import BaseTool, Tool
# from langchain.llms import LlamaCpp
# from langchain.callbacks.manager import CallbackManager
# from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
# from langchain.prompts import PromptTemplate
# from langchain.chains import LLMChain
# import magic # 用于识别文件类型(辅助文件搜索工具)
# from langchain.vectorstores import Chroma
# from langchain.embeddings import LlamaCppEmbeddings
# from langchain.text_splitter import RecursiveCharacterTextSplitter
# from langchain.document_loaders import (
# TextLoader, UnstructuredFileLoader, PDFMinerLoader,
# UnstructuredMarkdownLoader, Docx2txtLoader
# )
# from langchain.chains import RetrievalQA
# from langchain.memory import ConversationBufferWindowMemory
# from langchain.prompts import PromptTemplate
# from langchain.schema import HumanMessage, AIMessage
# from llama_cpp import Llama, LlamaGrammar
import json
import logging
from pathlib import Path
# import argparse
# import base64
import time
# from fastapi import FastAPI, HTTPException, APIRouter, Path
# from fastapi.responses import JSONResponse
# from fastapi_utils.cbv import cbv
# from fastapi_utils.inferring_router import InferringRouter
# from pydantic import BaseModel
# from PIL import Image
# import numpy as np
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
from models.models_server import Models_Server
from models.models_client import Models_Client
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
#模型管理器
class Models_Mag:
"""
模型管理器包含模型推理server和模型推理client
"""
def __init__(self):
#定义模型管理器的server和client
self.models_server = None
self.models_client = None
self.models_interface= None
def init_model_server(self,model_config:Optional[dict[str,Any]]):
"""
初始化模型推理server
参数:
返回:
初始化成功返回True,初始化失败返回False
"""
try:
self.models_server = Models_Server(model_config["models_server"])
return True
except Exception as e:
logger.error(f"models_server初始化失败: {str(e)}")
return False
def init_model_client(self,model_config:Optional[dict[str,Any]]):
"""
初始化模型推理client
参数:
返回:
初始化成功返回True,初始化失败返回False
"""
try:
self.models_client = Models_Client(model_config["models_client"]["server_url"])
return True
except Exception as e:
logger.error(f"models_server初始化失败: {str(e)}")
return False
def init_model_interface(self,
model_config:Optional[dict[str,Any]],
is_model_server:bool = False):
"""
初始化模型推理client
参数:
返回:
初始化成功返回True,初始化失败返回False
"""
try:
if is_model_server:
self.init_model_server(model_config=model_config)
self.models_interface = self.models_server
else:
self.init_model_client(model_config=model_config)
self.models_interface = self.models_client
return True
except Exception as e:
logger.error(f"models_server初始化失败: {str(e)}")
return False
if __name__=="__main__":
from tools.core.common_functions import read_json_file
from tools.data_process.image_process.image_process import decode_base64_image, preprocess_image, PIL_image_to_base64

View File

@@ -0,0 +1,757 @@
#!/usr/bin/env python3
import sys
import io
from typing import List, Dict, Any, Optional, Tuple
import multiprocessing
import json
import logging
from pathlib import Path
import time
from curses.panel import top_panel
from email import message
import os
# from langchain.agents import initialize_agent, AgentType
# from langchain.tools import BaseTool, Tool
# from langchain.llms import LlamaCpp
# from langchain.callbacks.manager import CallbackManager
# from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
# from langchain.prompts import PromptTemplate
# from langchain.chains import LLMChain
# import magic # 用于识别文件类型(辅助文件搜索工具)
# from langchain.vectorstores import Chroma
# from langchain.embeddings import LlamaCppEmbeddings
# from langchain.text_splitter import RecursiveCharacterTextSplitter
# from langchain.document_loaders import (
# TextLoader, UnstructuredFileLoader, PDFMinerLoader,
# UnstructuredMarkdownLoader, Docx2txtLoader
# )
# from langchain.chains import RetrievalQA
# from langchain.memory import ConversationBufferWindowMemory
# from langchain.prompts import PromptTemplate
# from langchain.schema import HumanMessage, AIMessage
from cv2 import log
from llama_cpp import Llama, LlamaGrammar
from llama_cpp.llama_chat_format import Llava15ChatHandler,Qwen25VLChatHandler
import argparse
import base64
from PIL import Image
import numpy as np
from fastapi import FastAPI, HTTPException, APIRouter, Depends
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi_utils.cbv import cbv
from fastapi_utils.inferring_router import InferringRouter
from sympy import content
import uvicorn
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
from tools.core.common_functions import read_json_file
from tools.data_process.image_process.image_process import decode_base64_image, preprocess_image,PIL_image_to_base64,PIL_image_to_base64_sizelmt
from models.model_definition import (TextInferenceRequest,MultimodalInferenceRequest,InferenceRequest,InferenceResponse,HealthResponse)
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
shared_state = multiprocessing.Manager().dict()
shared_state["model_loaded"] = False
shared_state["model_path"] = ""
shared_state["load_start_time"] = 0
shared_state["load_complete"] = False
# 进程内单例变量
_process_instance = None
_process_initialized = False
class Models_Server:
"""多模态大模型的服务"""
_instance = None
def __new__(cls, *args, **kwargs):
"""确保每个进程只有一个实例"""
# global _model_server_instance
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self,config):
"""
初始化模型服务,仅在首次实例化时加载模型
"""
global _process_instance, _process_initialized
# 每个进程只初始化一次
if _process_instance and _process_initialized:
self.model = _process_instance.model
# self.model_path = _process_instance.model_path
self.process_id = os.getpid()
return
#model: vlmllm
self.process_id = os.getpid()
self.model = None
self.model_loaded = False
self.load_start_time = 0.0
self.load_end_time = None
self.model_server_config = config
# self.inference_start_time = 0.0
# #定义服务器地址和端口
# self.model_server_host = None
# self.model_server_port = None
# self._wait_for_model_load(config)
# self.init_model(config["model_inference_framework_type"],config)
# 标记进程内初始化完成
_process_initialized = True
_process_instance = self
logger.info(f"model server init success! process_id :{self.process_id}")
# def parse_config(self,config_json_file):
# """解析model server的config文件"""
# self.model_server_config= read_json_file(config_json_file)
def _wait_for_model_load(self,config:Optional[dict[str,Any]]=None):
"""等待首个进程完成模型加载"""
if config is None:
config = self.model_server_config
if not shared_state["load_complete"]:
# 检查是否是第一个启动的进程
if shared_state["load_start_time"] == 0:
# 第一个进程负责设置加载状态
shared_state["load_start_time"] = time.time()
shared_state["model_path"] = config["vlm_model_path"]
else:
# 其他进程等待加载完成
print(f"进程 {self.process_id} 等待模型加载...")
while not shared_state["load_complete"]:
if time.time() - shared_state["load_start_time"] > 600: # 10分钟超时
raise Exception("模型加载超时")
time.sleep(2)
def init_vlm_llamacpp(self,config:Optional[dict[str,Any]]=None):
"""
初始化加载多模态大模型
"""
if config is None:
config = self.model_server_config
self.load_start_time = time.time()
try:
chat_handler=None
if config["multi_modal"]:
if "Qwen25VLChatHandler" == config["chat_handler"]:
chat_handler = Qwen25VLChatHandler(clip_model_path=config["mmproj_model_path"])
else :
logger.error("模型chat_format未知")
# 加载模型
model= Llama(
model_path=config["vlm_model_path"],
chat_handler = chat_handler,
n_ctx=config["n_ctx"], # 上下文窗口大小
n_threads=config["n_threads"], # 线程数
n_gpu_layers=config["n_gpu_layers"], # GPU层数量根据显卡调整
verbose=config["verbose"],# 减少日志输出
# chat_format=config["chat_format"] # 多模态模型通常使用llava格式
n_batch = config["n_batch"],
n_ubatch = config["n_ubatch"]
)
logger.info(f"模型加载成功,耗时: {time.time() - self.load_start_time:.2f}")
return model
except Exception as e:
logger.error(f"模型加载失败: {str(e)}")
return None
def init_model(self, model_inference_framework_type:Optional[str]=None,
config:Optional[dict[str,Any]]=None):
"""初始化模型"""
if config is None:
config = self.model_server_config
if model_inference_framework_type is None:
model_inference_framework_type = config["model_inference_framework_type"]
try:
logger.info(f"进程 {self.process_id} 开始加载模型...")
if "llama_cpp" == model_inference_framework_type:
self.model = self.init_vlm_llamacpp(config)
#TODO 其他加载模型的推理框架入口
else:
logger.error(f"加载模型的框架未知,加载模型失败。")
self.model_loaded = False
if self.model:
logger.info(" model load success!!!!")
self.model_loaded = True
# 如果是第一个完成加载的进程,更新共享状态
if not shared_state["load_complete"]:
shared_state["load_complete"] = True
shared_state["model_loaded"] = True
shared_state["load_time"] = time.time() - shared_state["load_start_time"]
logger.info(f"进程 {self.process_id} 模型加载完成")
else:
self.model_loaded = False
logger.error(f"进程 {self.process_id} 模型加载失败model 为 None")
except Exception as e:
print(f"进程 {self.process_id} 模型加载失败: {str(e)}")
raise
def _build_prompt(self, usr_prompt: str, system_prompt: Optional[str]) -> str:
"""构建符合QwenVL2格式的提示词"""
if system_prompt:
return f"SYSTEM:{system_prompt}\nUSER:{usr_prompt}\nASSISTANT:"
return f"USER:{usr_prompt}\nASSISTANT:"
def _build_message(self,request:InferenceRequest,config:Optional[dict[str,Any]]=None):
"""构建模型推理的message"""
if config is None:
config = self.model_server_config
try:
messages = []
if request.system_prompt:
system_message={
"role": "system",
"content": [request.system_prompt]
}
messages.append(system_message)
user_message = {
"role": "user",
"content": []
}
#加入用户消息
messages.append(user_message)
if "Qwen25VLChatHandler" == config["chat_handler"]:
for msg in messages:
if "user" == msg["role"]:
# 添加图像到消息
if request.image_data:
len_images = len(request.image_data)
print("len_images: ",len_images)
for i in range(0,len_images):
logger.info(f"add image {i}")
msg["content"].append(
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{request.image_data[i]}"}
#f"data:image/jpeg;base64,{request.image_data[i]}"
}
)
#加入user_prompt
msg["content"].append(
{"type": "text", "text": request.user_prompt}
)
else:
for msg in messages:
if "user" == msg["role"]:
# 添加图像到消息
if request.image_data:
len_images = len(request.image_data)
print("len_images: ",len_images)
for i in range(0,len_images):
logger.info("add image ",i)
msg["content"].append(
{
"type": "image",
"image": f"data:image/jpeg;base64,{request.image_data[i]}" #图像数据形式
#f"data:image/jpeg;base64,{request.image_data[i]}"
}
)
#加入user_prompt
msg["content"].append(
{"type": "text", "text": request.user_prompt}
)
return messages
except Exception as e:
print(f"进程 {self.process_id} 构建模型推理message: {str(e)}")
return None
def text_prompt_inference(self, request: TextInferenceRequest):
"""处理纯文本提示词非格式化推理"""
# global model, model_loaded
logger.info(f"model_loaded={self.model_loaded}!!flag1")
logger.info(f"model={self.model}!!flag1")
if not self.model_loaded or self.model is None:
logger.error(f"model_loaded={self.model_loaded}!!flag")
raise HTTPException(status_code=503, detail="模型未加载完成,请稍后再试")
try:
inference_start_time = time.time()
# 构建提示
prompt = f"USER: {request.user_prompt}\nASSISTANT:"
# 生成响应
output = self.model.create_completion(
prompt=prompt,
max_tokens=request.max_tokens,
temperature=request.temperature,
top_p=request.top_p,
stop=request.stop ,
stream=False
)
inference_time = time.time() - inference_start_time
result = output["choices"][0]["text"].strip()
return {
"result": result,
"metadata": {
"inference_time_seconds": inference_time,
"tokens_generated": len(self.model.tokenize(result.encode())),
"model": self.model.model_path.split("/")[-1],
"process_id": self.process_id
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"推理失败: {str(e)}")
def text_inference(self, request: TextInferenceRequest):
"""处理纯文本提示词格式化推理"""
# global model, model_loaded
logger.info(f"model_loaded={self.model_loaded}!!flag1")
logger.info(f"model={self.model}!!flag1")
if not self.model_loaded or self.model is None:
logger.error(f"model_loaded={self.model_loaded}!!flag")
raise HTTPException(status_code=503, detail="模型未加载完成,请稍后再试")
try:
inference_request = InferenceRequest(
user_prompt = request.user_prompt,
max_tokens = request.max_tokens,
temperature = request.temperature,
top_p = request.top_p,
system_prompt = request.system_prompt,
stop = request.stop
)
messages = self._build_message(inference_request,self.model_server_config)
inference_start_time = time.time()
# 构建提示
prompt = f"USER: {request.user_prompt}\nASSISTANT:"
# 生成响应
output = self.model.create_chat_completion(
messages=messages,
max_tokens=request.max_tokens,
temperature=request.temperature,
top_p=request.top_p,
stop=request.stop ,
stream=False
)
inference_time = time.time() - inference_start_time
result = output["choices"][0]["message"]["content"].strip()
return {
"result": result,
"metadata": {
"inference_time_seconds": inference_time,
"tokens_generated": len(self.model.tokenize(result.encode())),
"model": self.model.model_path.split("/")[-1],
"process_id": self.process_id
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"推理失败: {str(e)}")
def multimodal_inference(self,request: MultimodalInferenceRequest):
"""处理大模型多模态推理(文本+图像)"""
logger.info("multimodal_inference run")
if not self.model_loaded or self.model is None:
raise HTTPException(status_code=503, detail="模型未加载完成,请稍后再试")
try:
inference_start_time = time.time()
# 解码并预处理所有图像equest.image_data中图像是jpeg的base64格式
# processed_base64_list = []
# for img_b64 in request.image_data:
# try:
# image = decode_base64_image(img_b64)
# image.save("/home/ubuntu/Workspace/Projects/VLM_VLA/Datas/image/output_image.jpg")
# # 预处理(缩放、去透明通道等,确保模型兼容)
# processed_b64 = PIL_image_to_base64_sizelmt(image=image)
# # 将预处理后的图像重新编码为 Base64关键确保格式正确
# processed_base64_list.append(processed_b64)
# logger.info("multimodal_inference decode image and save!")
# # processed_img = preprocess_image(image)
# # processed_base64_list.append(processed_img)
# except Exception as e:
# raise HTTPException(status_code=400, detail=f"处理图像失败: {str(e)}")
# 构建多模态提示
messages = self._build_message(request,self.model_server_config)
len_images = len(request.image_data)
# print(f"full_prompt: {messages}")
# 生成响应
output = self.model.create_chat_completion(
messages=messages,
max_tokens=request.max_tokens,
temperature=request.temperature,
top_p=request.top_p,
stop=request.stop,
stream=False
)
inference_time = time.time() - inference_start_time
result = output["choices"][0]["message"]["content"].strip()
return {
"result": result,
"metadata": {
"inference_time_seconds": inference_time,
"num_images_processed": len_images,
"tokens_generated": len(self.model.tokenize(result.encode())),
"model": self.model.model_path.split("/")[-1]
}
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"多模态推理失败: {str(e)}")
def model_inference(self,request: InferenceRequest):
"""处理大模型(纯文本和多模态(文本+图像))推理"""
logger.info("model_inference run")
if not self.model_loaded or self.model is None:
raise HTTPException(status_code=503, detail="模型未加载完成,请稍后再试")
try:
inference_start_time = time.time()
messages = self._build_message(request,self.model_server_config)
len_images = len(request.image_data)
# print(f"full_prompt: {messages}")
# 生成响应
output = self.model.create_chat_completion(
messages=messages,
max_tokens=request.max_tokens,
temperature=request.temperature,
top_p=request.top_p,
stop=request.stop,
stream=False
)
inference_time = time.time() - inference_start_time
result = output["choices"][0]["message"]["content"].strip()
return {
"result": result,
"metadata": {
"inference_time_seconds": inference_time,
"num_images_processed": len_images,
"tokens_generated": len(self.model.tokenize(result.encode())),
"model": self.model.model_path.split("/")[-1]
}
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"多模态推理失败: {str(e)}")
##############################################################################################################################
model_router = InferringRouter() # 支持自动推断参数类型
# # 依赖注入:获取单例模型服务
def get_model_server() -> Models_Server:
"""依赖注入:获取当前进程的单例服务实例"""
logger.info(f"=== get_model_server 被触发Process ID: {os.getpid()} ===")
if _process_instance is None:
config_json_file= f"{project_root}/models/model_config.json"
print("config_json_file: ",config_json_file)
# global model_server_config
model_server_config = read_json_file(config_json_file)["models_server"][0]
if model_server_config is None:
logger.error("get_model_server: 配置未初始化!")
raise HTTPException(status_code=500, detail="服务配置未初始化")
# 获取单例实例
model_server = Models_Server(model_server_config)
if not model_server.model_loaded:
model_server._wait_for_model_load(model_server_config)
model_server.init_model(model_server_config["model_inference_framework_type"],
model_server_config)
# _process_initialized = True
else:
model_server = _process_instance
if not model_server.model_loaded:
model_server._wait_for_model_load(model_server_config)
model_server.init_model(model_server_config["model_inference_framework_type"],
model_server_config)
logger.info(f"get_model_server 返回实例状态: 加载={model_server.model_loaded}, Process ID={model_server.process_id}")
return model_server
@cbv(model_router) # 将类与路由绑定
class Models_Controller:
def __init__(self, model_server: Models_Server = Depends(get_model_server)):
self.model_server = model_server # 实例变量,确保每个请求可用
logger.info(f"Models_Controller init success! Process ID: {os.getpid()}")
"""
初始化成员变量
"""
#model: vlmllm
# model_server = Depends(get_model_server)
# self.model_loaded = False
# self.load_start_time = 0.0
# self.inference_start_time = 0.0
# #定义服务器地址和端口
# self.model_server_host = None
# self.model_server_port = None
# logger.info("Models_Controller init success!")
# def parse_config(self,config_path):
def set_model_server_host(self,model_server_host):
"""
设置模型推理服务器的地址
"""
try:
self.model_server_host = model_server_host
return True
except Exception as e:
logger.error(f"设置模型推理服务器的地址失败: {str(e)}")
return False
def set_model_server_port(self,model_server_port):
"""
设置模型推理服务器的端口
"""
try:
self.model_server_port = model_server_port
return True
except Exception as e:
logger.error(f"设置模型推理服务器的端口失败: {str(e)}")
return False
@model_router.post("/text/inference", response_model=Dict[str, Any])
async def text_inference(self, request: TextInferenceRequest):
"""纯文本推理端点"""
return self.model_server.text_inference(request)
@model_router.post("/multimodal/inference", response_model=Dict[str, Any])
async def multimodal_inference(self,request: MultimodalInferenceRequest):
"""大模型多模态推理(文本+图像)端点"""
return self.model_server.multimodal_inference(request)
@model_router.post("/model/inference", response_model=Dict[str, Any])
async def model_inference(self,request: InferenceRequest):
"""大模型(纯文本或多模态(文本+图像))推理端点"""
return self.model_server.model_inference(request)
# API端点
@model_router.get("/model/health", response_model=Dict[str, Any])
async def model_health_check(self):
"""检查大模型服务健康状态"""
return {
"status": "healthy" if shared_state["model_loaded"] else "loading",
"model_loaded": shared_state["model_loaded"],
"process_id": self.model_server.process_id,
"load_time": shared_state.get("load_time", 0),
"model_path": shared_state.get("model_path", "")
}
# def start_http_model_controller( model_server_host="localhost",model_server_port=8000,model_workers=1):
# """
# 初始化并启用FastAPI应用
# """
# # #初始化加载模型
# # self.init_model(config["model_inference_framework_type"],config)
# # 初始化FastAPI应用
# model_app = FastAPI(title="多模态大模型推理服务")
# # 配置CORS
# # model_app.add_middleware(
# # CORSMiddleware,
# # allow_origins=["*"],
# # allow_credentials=True,
# # allow_methods=["*"],
# # allow_headers=["*"],
# # )
# try:
# # 将路由注册到 app
# model_app.include_router(model_router)
#
# uvicorn.run(app=model_app, host=model_server_host, port=model_server_port,workers=model_workers)
# return True
# except Exception as e:
# logger.error(f"初始化并启用FastAPI应用失败: {str(e)}")
# return False
def create_app() -> FastAPI:
"""创建FastAPI应用"""
app = FastAPI(title="Multimodal API", version="1.0")
# 配置CORS
# app.add_middleware(
# CORSMiddleware,
# allow_origins=["*"],
# allow_credentials=True,
# allow_methods=["*"],
# allow_headers=["*"],
# )
# 注册路由
app.include_router(model_router)
return app
def main():
#model server 和client初始化
# "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/models/model_config.json"
print(f"project_root111:{project_root}")
config_json_file= f"{project_root}/models/model_config.json"
print("config_json_file: ",config_json_file)
# global model_server_config
model_server_config = read_json_file(config_json_file)["models_server"][0]
uvicorn.run(
app="models_server:create_app",
host=model_server_config["model_server_host"],
port=model_server_config["model_server_port"],
workers=model_server_config["model_controller_workers"],
factory=True,
log_level="info"
)
def test_inference(inference_type=0):
from tools.core.common_functions import read_json_file
config_json_file="/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/models/model_config.json"
model_server_config = read_json_file(config_json_file)["models_server"][0]
model_server = Models_Server(model_server_config)
model_server._wait_for_model_load(model_server_config)
if not model_server.model_loaded:
model_server.init_model(model_inference_framework_type=model_server_config["model_inference_framework_type"],
config=model_server_config)
system_prompt = ""
if 2==inference_type:
prompt = "检测图中的蓝色气球,并按照固定的结构(<|object_ref_start|>蓝色气球<|object_ref_end|><|box_start|>(xmin,ymin),(xamx,ymax)<|box_end|>)给出2D框坐标"
# "请对图片进行场景描述!"
# "请检测图片中蓝色的气球请给出准确的2D坐标框的坐标"
# "检测图中的蓝色气球,并按照固定的结构(<|object_ref_start|>蓝色气球<|object_ref_end|><|box_start|>(xmin,ymin),(xamx,ymax)<|box_end|>)给出2D框坐标"
test_image_path = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/datas/test1/balloon2.jpg"
img_base64 = PIL_image_to_base64_sizelmt(image_path=test_image_path,need_size_lmt=False)
multimodal_request = MultimodalInferenceRequest(
user_prompt = prompt,
image_data = [img_base64],
max_tokens = 24576,
temperature = 0.7,
top_p = 0.95,
system_prompt= system_prompt,
stop = None)
response = model_server.multimodal_inference(request=multimodal_request)
logger.info(response)
elif 1==inference_type:
prompt = "请你描述一下《西游记》中孙悟空的特点!"
text_request = TextInferenceRequest(
user_prompt= prompt,
max_tokens= 2048,
temperature = 0.7,
top_p= 0.95,
system_prompt= system_prompt,
stop = None)
response = model_server.text_inference(request=text_request)
logger.info(response)
elif 0==inference_type:
prompt = "请你描述一下《西游记》中孙悟空的特点!"
text_request = TextInferenceRequest(
user_prompt= prompt,
max_tokens= 2048,
temperature = 0.7,
top_p= 0.95,
system_prompt= system_prompt,
stop = None)
response = model_server.text_prompt_inference(request=text_request)
logger.info(response)
if __name__ == "__main__":
# main()
# test_inference(2)
logger.info("work finish")

View File

Binary file not shown.

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3
from atexit import register
import os
import sys
import logging
import asyncio
from langchain.memory import ConversationBufferWindowMemory
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from pydantic import SecretStr
import json
from pathlib import Path
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
from tools.core.common_functions import read_json_file
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
class PNC:
def __init__(self):
print("PNC 初始化完成")

View File

@@ -0,0 +1,4 @@
# ros uav agent node param
uav_agent:
agent_config_json_file:

View File

@@ -0,0 +1,487 @@
#!/usr/bin/env python3
import os
import sys
from typing import List, Dict, Any, Optional, Tuple
from pathlib import Path
import threading
import time
import numpy as np
import cv2
import rospy # type: ignore
from std_msgs.msg import String # type: ignore
from sensor_msgs.msg import Image as Sensor_Image # type: ignore
from cv_bridge import CvBridge, CvBridgeError # pyright: ignore[reportMissingImports]
#自定义数据结构
from iscas_msgs.msg import LLMDetectCommand
import logging
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
from scripts.ai_agent.agents.multimodal_large_model_agent import MMLM_Agent
from tools.core.common_functions import read_yaml_file
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# ------------------------------
# ROS1节点封装
# ------------------------------
class MultimodalAgentROSNode:
"""将多模态Agent封装为ROS1节点提供ROS接口"""
def __init__(self):
# 初始化ROS节点
rospy.init_node('multimodal_uav_agent', anonymous=False)
# 从参数服务器获取配置,提供默认值
# self.vlm_model_path = rospy.get_param(
# 'uav_agent/vlm_model_path',
# '/path/to/your/llm/model.gguf'
# )
# self.embedding_model_path = rospy.get_param(
# 'uav_agent/embedding_model_path',
# '/path/to/your/embedding/model.gguf'
# )
# self.max_context_window = rospy.get_param('uav_agent/max_context_window', 5)
# self.max_tokens = rospy.get_param('uav_agent/max_tokens', 512)
# self.persist_directory = rospy.get_param(
# 'uav_agent/persist_directory',
# os.path.join(os.path.expanduser("~"), ".multimodal_agent", "chroma_db")
# )
# # 从参数服务器获取配置,提供默认值
# self.vlm_model_path = rospy.get_param(
# param_name='uav_agent/agent_config_json_file',
# default='/path/to/your/llm/model.gguf'
# )
# uav_ai_agent_node_params = read_yaml_file(yaml_path=f"{current_script.parents[0]}/ros_uav_agent_node_param.yaml")
# # 验证模型路径
# if not os.path.exists(self.vlm_model_path):
# rospy.logwarn(f"VLM模型路径不存在: {self.vlm_model_path}")
# if not os.path.exists(self.embedding_model_path):
# rospy.logwarn(f"嵌入模型路径不存在: {self.embedding_model_path}")
# 创建持久化目录
# os.makedirs(self.persist_directory, exist_ok=True)
# agent_config_json_file = uav_ai_agent_node_params.get("agent_config_json_file")
agent_config_json_file= f"{current_script.parents[1]}/config/agent_config.json"
# 初始化Agent
try:
self.ai_agent = MMLM_Agent(
# vlm_model_path=self.vlm_model_path,
# embedding_model_path=self.embedding_model_path
# max_context_window=self.max_context_window,
# max_tokens=self.max_tokens,
# persist_directory=self.persist_directory
)
self.ai_agent.parse_config(config_json_file=agent_config_json_file)
self.ai_agent.init_components(agent_config=self.ai_agent.agent_config)
except Exception as e:
rospy.logfatal(f"Agent初始化失败: {str(e)}")
raise
# 初始化订阅者
self.text_query_sub = rospy.Subscriber(
'/uav_agent/text_query', # 使用私有话题
String,
self.text_query_callback,
queue_size=1
)
self.image_sub = rospy.Subscriber(
'/uav_agent/image_input',
Sensor_Image,
self.image_callback,
queue_size=1
)
self.add_document_sub = rospy.Subscriber(
'/uav_agent/add_document',
String,
self.add_document_callback,
queue_size=1
)
self.clear_context_sub = rospy.Subscriber(
'/uav_agent/clear_context',
String,
self.clear_context_callback,
queue_size=1
)
self.object_detect_sub = rospy.Subscriber(
'/fastsystem/detect',
LLMDetectCommand,
self.object_detect_callback,
queue_size=1
)
# 初始化发布者
self.response_pub = rospy.Publisher(
'/uav_agent/response',
String,
queue_size=10
)
self.status_pub = rospy.Publisher(
'/uav_agent/status',
String,
queue_size=10
)
self.obj_detect_response_pub = rospy.Publisher(
'/slowsystem/detect_result',
String,
queue_size=10
)
# 存储最新的图像
self.latest_image = None
self.image_lock = threading.Lock() # 线程安全锁
# 发布初始化完成状态
rospy.loginfo("多模态RAG Agent节点初始化完成")
self.status_pub.publish("状态: 初始化完成,就绪")
# 定时发布状态
self.status_timer = rospy.Timer(rospy.Duration(10), self.publish_heartbeat)
def publish_heartbeat(self, event):
"""定时发布心跳状态"""
self.status_pub.publish("状态: 运行中")
def text_query_callback(self, msg: String) -> None:
"""处理文本查询回调"""
query = msg.data
if not query.strip():
rospy.logwarn("收到空的文本查询,忽略")
self.status_pub.publish("警告: 收到空查询")
return
rospy.loginfo(f"收到文本查询: {query[:50]}...") # 只显示前50字符
self.status_pub.publish(f"处理查询: {query[:30]}...")
# 获取最新图像(如果有)
image_data = None
with self.image_lock:
if self.latest_image is not None:
image_data = self.latest_image
self.latest_image = None # 处理后清除
rospy.loginfo("使用最新的图像数据进行处理")
# 在单独的线程中处理查询避免阻塞ROS回调
threading.Thread(
target=self._process_query_thread,
args=(query, image_data),
daemon=True
).start()
def object_detect_callback(self, msg: LLMDetectCommand) -> None:
"""处理目标检测的回调"""
target_decription = f"{msg.amount}{msg.property}{msg.target_class}"
if not target_decription.strip():
rospy.logwarn("收到空的文本查询,忽略")
self.status_pub.publish("警告: 收到空查询")
return
obj_detect_prompt_template= self.ai_agent.prompt_mag.create_object_detect_prompt_template()
obj_detect_prompt = obj_detect_prompt_template.format(object_description=target_decription)
# final_prompt_template = self.ai_agent.prompt_mag.create_final_conversation_prompt_template()
# final_prompt = obj_detect_prompt.format(model_role="AI专家",
# field="目标检测",
# context="",
# chat_history = "",
# question=obj_detect_prompt)
rospy.loginfo(f"收到文本查询: {obj_detect_prompt[:50]}...") # 只显示前50字符
self.status_pub.publish(f"处理查询: {obj_detect_prompt[:30]}...")
# 获取最新图像(如果有)
#sensor_image 2 dict
image_data = self.ai_agent.multimodal_processor.ros_image2dict( image_msg=msg.image)
# with self.image_lock:
# if self.latest_image is not None:
# image_data = self.latest_image
# self.latest_image = None # 处理后清除
# rospy.loginfo("使用最新的图像数据进行处理")
obj_detect_query_info = {
"model_role":"AI专家",
"field":"图像目标检测",
"user_query":obj_detect_prompt
}
query_type = "obj_detect"
# 在单独的线程中处理查询避免阻塞ROS回调
threading.Thread(
target=self._process_query_thread,
args=(query_type,obj_detect_query_info,image_data),
daemon=True
).start()
def _process_query_thread(self, query_type:str, query_info: Optional[dict[str,Any]], image_data: Optional[Dict[str, Any]]) -> None:
"""在单独线程中处理查询"""
try:
response = self.ai_agent.process_query(query_info = query_info,image_data = image_data)
# self.response_pub.publish(response)
if "obj_detect" == query_type:
self.obj_detect_response_pub.publish(response)
#TODO
else:
self.response_pub.publish(response)
self.status_pub.publish("查询处理完成")
except Exception as e:
error_msg = f"查询处理线程出错: {str(e)}"
rospy.logerr(error_msg)
self.response_pub.publish(error_msg)
self.status_pub.publish(f"错误: {str(e)}")
def image_callback(self, msg: Sensor_Image) -> None:
"""处理图像输入回调"""
rospy.loginfo(f"收到图像输入,尺寸: {msg.width}x{msg.height}")
try:
# 处理图像并存储
image_data = self.ai_agent.multimodal_processor.ros_image2dict(msg)
with self.image_lock:
self.latest_image = image_data
self.status_pub.publish(f"已接收图像,尺寸: {msg.width}x{msg.height},等待文本查询")
except Exception as e:
rospy.logerr(f"处理图像失败: {str(e)}")
self.status_pub.publish(f"图像处理失败: {str(e)}")
def add_document_callback(self, msg: String) -> None:
"""添加文档到知识库回调"""
file_path = msg.data
rospy.loginfo(f"收到添加文档请求: {file_path}")
self.status_pub.publish(f"正在处理文档: {os.path.basename(file_path)}")
# 在单独线程中处理文档添加
threading.Thread(
target=self._add_document_thread,
args=(file_path,),
daemon=True
).start()
def _add_document_thread(self, file_path: str) -> None:
"""在单独线程中添加文档"""
success, message = self.uav_agent.add_knowledge_document(file_path)
self.status_pub.publish(message)
rospy.loginfo(f"文档处理结果: {message}")
def clear_context_callback(self, msg: String) -> None:
"""清除上下文回调"""
rospy.loginfo("收到清除上下文请求")
self.uav_agent.clear_context()
self.status_pub.publish("对话上下文已清除")
# async def uav_conconversation(self):
# """
# 主函数 - 基于LangChain UAV agent对话客户端
# """
# try:
# tools = await self.mcp_clients_mag.multi_server_mcp_client.get_tools()
# # 创建带有智能提示词的代理
# prompt_temp = """你是一个专业的智能助手,具备多种工具能力,能够帮助用户完成各类任务。
# 可用工具及使用场景:
# - terminal: 执行系统命令、文件操作、程序运行
# - app_analysis: 职业分析、应用程序分析、用户画像
# - browser: 网页控制、自动化操作、信息采集
# - weather: 天气查询、气象信息获取
# 工具使用原则:
# 1. 只有用户明确需要执行具体操作时才使用工具
# 2. 对于一般性对话、概念解释、建议咨询等,直接回答
# 3. 优先选择最合适的工具来完成任务
# 4. 如果不确定是否需要工具,优先选择直接回答
# 请保持自然、友好的对话风格,准确理解用户真实意图,明智地选择是否使用工具。"""
# template_variable_name = "message"
# smart_prompt = self.prompt_mag.create_chat_prompt_template(prompt_temp,
# template_variable_name)
# react_agent = create_react_agent(self.model_mag.models_server.model,
# tools, prompt=smart_prompt)
# # # 使用窗口记忆 - 保留最近10轮对话无需transformers包
# # memory = ConversationBufferWindowMemory(
# # k=10, # 保留最近10轮对话
# # return_messages=True # 返回消息对象
# # )
# print("✨ 全功能智能助手已启动!")
# print("🔧 完整工具: 系统命令 | 职业分析 | 浏览器控制 | 天气查询")
# print("🧠 AI会智能判断何时需要调用工具")
# print("💬 支持流式对话,体验更自然")
# print("输入 'exit' 退出\n")
# # 对话循环
# while True:
# user_input = input("💬 问题: ").strip()
# if user_input.lower() == "exit":
# print("👋 再见!")
# break
# if not user_input:
# continue
# # 获取历史消息并添加当前用户输入
# messages = memory.chat_memory.messages.copy()
# messages.append(HumanMessage(content=user_input))
# # 先用LLM判断是否需要工具
# print("🤔 分析问题中...")
# # 创建判断提示
# judge_prompt = f"""
# 用户问题: {user_input}
# 请判断这个问题是否需要调用工具来完成任务。
# 需要调用工具的情况(回答YES)
# - 执行系统命令、文件操作、程序运行
# - 职业分析、应用程序分析、了解职业匹配度
# - 网页控制、信息采集、打开浏览器
# - 天气查询、气象信息、天气预报
# - 其他需要实际执行操作的任务
# 不需要工具的情况(回答NO)
# - 一般性对话、问候、闲聊
# - 概念解释、知识问答
# - 建议咨询、意见交流
# - 创意写作、故事创作
# - 简单数学计算
# 只回答YES或NO不要其他内容。
# """
# judge_response = await llm.ainvoke([HumanMessage(content=judge_prompt)])
# need_tools = "YES" in str(judge_response.content).upper()
# if need_tools:
# print("🔧 检测到需要工具调用,启动增强模式...")
# ai_response = ""
# tool_used = False
# # 使用agent处理工具调用
# async for chunk in agent.astream({"messages": messages}):
# for node_name, node_output in chunk.items():
# if node_name == "agent":
# messages_in_chunk = node_output.get("messages", [])
# for msg in messages_in_chunk:
# if msg.type == "ai":
# if hasattr(msg, "tool_calls") and msg.tool_calls:
# tool_used = True
# for tool_call in msg.tool_calls:
# print(f"🔧 调用工具: {tool_call['name']}")
# print(f"📝 参数: {tool_call['args']}")
# elif msg.content:
# ai_response = msg.content
# elif node_name == "tools":
# messages_in_chunk = node_output.get("messages", [])
# for msg in messages_in_chunk:
# if msg.type == "tool":
# print(f"✅ 工具 '{msg.name}' 执行完成")
# try:
# import json
# result = json.loads(msg.content) if isinstance(msg.content, str) else msg.content
# if isinstance(result, dict):
# if result.get("success"):
# stdout = result.get("stdout", "").strip()
# if stdout:
# print(f"📄 命令输出:\n{stdout}")
# else:
# stderr = result.get("stderr", "")
# print(f"❌ 错误: {stderr}")
# except:
# print(f"📄 原始结果: {msg.content}")
# if ai_response:
# print(f"\n🎯 最终回答: {ai_response}")
# else:
# print("💭 使用流式回答模式...")
# print("\n🎯 AI回答: ", end="", flush=True)
# # 直接使用LLM流式生成
# ai_response = ""
# async for chunk in llm.astream(messages):
# if chunk.content:
# content_str = str(chunk.content)
# print(content_str, end="", flush=True)
# ai_response += content_str
# print() # 换行
# # 保存对话到记忆
# if ai_response:
# memory.save_context(
# {"input": user_input},
# {"output": str(ai_response)}
# )
# print("✓ 对话已保存到记忆")
# else:
# print("⚠️ 未获取到AI回复")
# except Exception as e:
# print(f"❌ 初始化失败: {e}")
# sys.exit(1)
# pass
def run(self) -> None:
"""运行节点"""
try:
rospy.spin()
except KeyboardInterrupt:
rospy.loginfo("节点被用户中断")
except Exception as e:
rospy.logfatal(f"节点运行出错: {str(e)}")
finally:
rospy.loginfo("节点关闭")
self.status_pub.publish("状态: 已关闭")
def main():
try:
node = MultimodalAgentROSNode()
node.run()
except rospy.ROSInterruptException:
rospy.logerr("ROS节点初始化失败")
except Exception as e:
rospy.logfatal(f"程序致命错误: {str(e)}")
# ------------------------------
# 主函数
# ------------------------------
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,159 @@
#!/usr/bin/env python3
import os
import sys
from typing import List, Dict, Any, Optional, Tuple, Union
import logging
import json
import yaml
def read_json_file(file_path):
"""
读取mcp_client_config的JSON文件并返回解析后的数据
参数:
file_path: JSON文件的路径
返回:
解析后的JSON数据通常是字典或列表如果出错则返回None
"""
try:
# 使用with语句打开文件确保文件会被正确关闭
with open(file_path, 'r', encoding='utf-8') as file:
# 加载并解析JSON数据
json_data = json.load(file)
print(f"成功读取JSON文件: {file_path}")
return json_data
except FileNotFoundError:
print(f"错误: 找不到文件 '{file_path}'")
except json.JSONDecodeError:
print(f"错误: 文件 '{file_path}' 不是有效的JSON格式")
except PermissionError:
print(f"错误: 没有权限读取文件 '{file_path}'")
except Exception as e:
print(f"读取JSON文件时发生错误: {str(e)}")
return None
# def persist_context_ndjson(self,
# persist_directory:str,
# rw_mode:str="w",
# encoding:str="utf-8",
# start:Optional[int] = None,
# end:Optional[int] = None):
# if start is not None:
# S_idx = start if start > 0 else 0
# else:
# S_idx = 0
# chat_history_len = len(self.chat_history)
# if end is not None:
# E_idx = end if end < chat_history_len else chat_history_len - 1
# else:
# E_idx = chat_history_len - 1
# #保存到NDJSON文件
# with open(file=persist_directory, mode=rw_mode, encoding=encoding) as f:
# for i in range(S_idx,E_idx) :
# item = self.chat_history[i]
# if isinstance(item, HumanMessage):
# context_line={"用户": item.content}
# # context += f"用户: {item.content}\n"
# elif isinstance(item, AIMessage):
# context_line={"助手": item.content}
# # context += f"助手: {item.content}\n"
# elif isinstance(item, dict) and item["type"] == "image":
# # context += f"用户发送了一张图像: {item['description']}\n"
# context_line={"用户": f"用户发送了一张图像: {item['description']}"}
# # context_ndjson.append(context_line)
# json_str = json.dumps(context_line, ensure_ascii=False)
# f.write(json_str + "\n")
from pathlib import Path
def persist_listdata2ndjson(data_list:Optional[dict[str,Any]],
file_path:str,
rw_mode:str="w",
encoding="utf-8"
):
"""
将列表数据保存为NDJSON文件每行一个JSON对象
:param data_list: 要保存的列表元素需为可JSON序列化的对象如dict
:param file_path: 输出的NDJSON文件路径
"""
# 确保文件目录存在(若路径包含未创建的目录,自动创建)
Path(file_path).parent.mkdir(parents=True, exist_ok=True)
with open(file=file_path, mode=rw_mode, encoding=encoding) as f:
for index, item in enumerate(data_list):
try:
# 将列表元素转为JSON字符串保留中文不转义
json_str = json.dumps(item, ensure_ascii=False)
# 写入字符串并换行确保每行一个JSON对象
f.write(json_str + "\n")
except json.JSONDecodeError as e:
# 捕获序列化失败的元素,提示索引便于排查
print(f"警告:列表第 {index} 个元素无法序列化为JSON已跳过 - 错误:{e}")
def persist_list2json(data_list:Optional[dict[str,Any]],
file_path:str,
rw_mode:str="w",
encoding="utf-8"
):
"""
将包含字典的列表保存为JSON文件
:param data_list: 待保存的列表(元素为字典)
:param file_path: 输出的JSON文件路径
"""
# 确保文件目录存在(若路径包含未创建的目录,自动创建)
Path(file_path).parent.mkdir(parents=True, exist_ok=True)
with open(file=file_path, mode=rw_mode, encoding=encoding) as f:
json.dump(obj=data_list, fp=f, ensure_ascii=False, indent=4)
def read_yaml_file(yaml_path:str,
rw_mode:str="r",
encoding:str="utf-8"):
"""
加载并解析YAML文件返回参数字典
Args:
yaml_path (str): YAML文件路径
Returns:
dict: 解析后的参数字典(若解析失败返回空字典)
"""
# 检查文件是否存在
if not Path(yaml_path).exists():
print(f"错误YAML文件不存在 - {yaml_path}")
return {}
try:
# 打开并解析YAML文件使用safe_load避免安全风险
with open(file=yaml_path, mode=rw_mode, encoding=encoding) as f:
params = yaml.safe_load(stream=f) # safe_load支持标准YAML类型拒绝执行代码
# 检查解析结果是否为字典YAML根节点通常是字典
if not isinstance(params, dict):
print("错误YAML文件根节点必须是字典")
return {}
return params
except yaml.YAMLError as e:
# 处理YAML格式错误如缩进错误、语法错误
print(f"YAML解析错误{e}")
return {}
except Exception as e:
# 处理其他异常(如权限问题)
print(f"加载YAML文件失败{e}")
return {}

View File

@@ -0,0 +1,209 @@
#!/usr/bin/env python3
import os
import sys
import io
from typing import List, Dict, Any, Optional, Tuple, Union
import logging
from pathlib import Path
from PIL import Image as PIL_Image
import base64
import numpy as np
import rospy # type: ignore
from std_msgs.msg import String # type: ignore
from sensor_msgs.msg import Image as Sensor_Image # type: ignore
from cv_bridge import CvBridge, CvBridgeError # type: ignore
import cv2
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[2] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# 图像处理工具函数
def decode_base64_image(base64_str: str) -> PIL_Image.Image:
"""将base64编码的字符串解码为PIL图像"""
try:
image_data = base64.b64decode(base64_str)
image = PIL_Image.open(io.BytesIO(image_data)).convert("RGB")
return image
except Exception as e:
raise ValueError(f"图像解码失败: {str(e)}")
def preprocess_image(image: PIL_Image.Image, need_resize: bool= False, target_size: tuple = (336, 336)) -> np.ndarray:
"""预处理图像以适应模型输入要求"""
# 调整大小
if need_resize:
image = image.resize(target_size, PIL_Image.Resampling.LANCZOS)
# 转换为numpy数组
image_np = np.array(image, dtype=np.float32)
# 归一化(根据模型要求调整)
image_np = image_np / 255.0
return image_np
from io import BytesIO
def PIL_image_to_base64( image_path: Optional[str] = None, image: Optional[PIL_Image.Image] = None) :
"""
将图像转换为base64编码
参数:
image_path: 图像文件路径
image: PIL Image对象
返回:
base64编码字符串
"""
if image is None:
if not image_path:
raise ValueError("必须提供图像路径或图像对象")
image = PIL_Image.open(image_path).convert("RGB")
# 转换为JPEG并编码
buffered = BytesIO()
image.save(buffered, format="JPEG", quality=90)
return base64.b64encode(buffered.getvalue()).decode('utf-8')
def ros_image2pil_image(self, ros_image_msg:Sensor_Image,supported_image_formats):
"""回调函数将ROS Image转为PIL Image并处理"""
try:
bridge_cv = CvBridge()
# 1. 将ROS Image消息转为OpenCV格式默认BGR8编码
# 若图像编码不同如rgb8需指定格式bridge.imgmsg_to_cv2(ros_image_msg, "rgb8")
# 尝试转换为OpenCV格式
if ros_image_msg.encoding in supported_image_formats:
cv_image = bridge_cv.imgmsg_to_cv2(ros_image_msg, desired_encoding=ros_image_msg.encoding)
else:
# 尝试默认转换
cv_image = bridge_cv.imgmsg_to_cv2(ros_image_msg)
logger.warning(f"转换不支持的图像编码: {ros_image_msg.encoding}")
# 2. OpenCV默认是BGR格式转为PIL需要的RGB格式
rgb_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
# 3. 转为PIL Image格式
pil_image = PIL_Image.fromarray(rgb_image)
# 此处可添加对PIL Image的处理如显示、保存等
rospy.loginfo(f"转换成功PIL Image尺寸 {pil_image.size}")
return pil_image
# 示例显示图像需要PIL的显示支持
# pil_image.show()
except CvBridgeError as e:
rospy.logerr(f"转换失败:{e}")
except Exception as e:
rospy.logerr(f"处理错误:{e}")
def ros_image2dict(self, image_msg: Sensor_Image,
supported_image_formats=['bgr8', 'rgb8', 'mono8'],
max_dim:int =2000) :
"""
将ROS图像消息转换为字典格式便于在上下文中存储
参数:
image_msg: ROS的Image消息
返回:
包含图像信息的字典或None(转换失败时)
"""
try:
# 尝试转换为OpenCV格式
if image_msg.encoding in self.supported_image_formats:
cv_image = self.bridge.imgmsg_to_cv2(image_msg, desired_encoding=image_msg.encoding)
else:
# 尝试默认转换
cv_image = self.bridge.imgmsg_to_cv2(image_msg)
logger.warning(f"转换不支持的图像编码: {image_msg.encoding}")
# 图像预处理:调整大小以减少数据量
h, w = cv_image.shape[:2]
if max(h, w) > max_dim:
scale = max_dim / max(h, w)
cv_image = cv2.resize(
cv_image,
(int(w * scale), int(h * scale)),
interpolation=cv2.INTER_AREA
)
# 转换为JPEG并编码为base64
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 80]
_, buffer = cv2.imencode('.jpg', cv_image, encode_param)
img_base64 = base64.b64encode(buffer).decode('utf-8')
return {
"type": "image",
"format": "jpg",
"data": img_base64,
"width": cv_image.shape[1],
"height": cv_image.shape[0],
"original_encoding": image_msg.encoding,
"timestamp": image_msg.header.stamp.to_sec()
}
except CvBridgeError as e:
logger.error(f"CV桥接错误: {str(e)}")
except Exception as e:
logger.error(f"图像处理错误: {str(e)}")
return None
def PIL_image_to_base64_sizelmt(image_path:Optional[str]=None,image:Optional[PIL_Image.Image] = None,
need_size_lmt:bool= False, max_size:tuple=(800, 800), quality:float=90):
"""处理图像并转换为Base64编码"""
try:
if image is None:
if not image_path:
raise ValueError("必须提供图像路径或图像对象")
# with PIL_Image.open(image_path) as image:
image = PIL_Image.open(image_path) #.convert("RGB")
if need_size_lmt:
# 缩放图像以控制大小避免Token超限
image.thumbnail(max_size,PIL_Image.Resampling.LANCZOS)
# 处理透明通道Qwen2.5-VL对RGBA支持有限
if image.mode in ('RGBA', 'LA'):
background = PIL_Image.new(image.mode[:-1], image.size, (255, 255, 255))
background.paste(image, mask=image.split()[-1])
image = background
elif image.mode == 'P':
image = image.convert('RGB')
elif image.mode != 'RGB':
image = image.convert('RGB')
# 保存为JPEG并编码为Base64
buffer = io.BytesIO()
image.save(buffer, format="JPEG", quality=quality)
return base64.b64encode(buffer.getvalue()).decode('utf-8')
# buffered = BytesIO()
# image.save(buffered, format="JPEG", quality=90)
# return base64.b64encode(buffered.getvalue()).decode('utf-8')
except Exception as e:
print(f"图像处理错误: {str(e)}")
return None

View File

@@ -0,0 +1,101 @@
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_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)
if 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()

View File

@@ -0,0 +1,125 @@
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 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()

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import json
# --- Configuration ---
# The base URL of your running FastAPI service
BASE_URL = "http://127.0.0.1:8000"
# The endpoint we want to test
ENDPOINT = "/generate_plan"
# The user prompt we will send for the test
TEST_PROMPT = "无人机从信息化建设管理服务中心起飞,飞到学生宿舍10幢降落"
def test_generate_plan():
"""
Sends a request to the /generate_plan endpoint and validates the response.
"""
url = BASE_URL + ENDPOINT
payload = {"user_prompt": TEST_PROMPT}
headers = {"Content-Type": "application/json"}
print("--- API Test: Generate Plan ---")
print(f"✅ URL: {url}")
print(f"✅ Sending Prompt: \"{TEST_PROMPT}\"")
try:
# Send the POST request
response = requests.post(url, data=json.dumps(payload), headers=headers)
# Check for HTTP errors (e.g., 404, 500)
response.raise_for_status()
# Parse the JSON response
data = response.json()
print("✅ Received Response:")
print(json.dumps(data, indent=2, ensure_ascii=False))
# --- Validation ---
print("\n--- Validation Checks ---")
# 1. Check if the response is a dictionary
if isinstance(data, dict):
print("PASS: Response is a valid JSON object.")
else:
print("FAIL: Response is not a valid JSON object.")
return
# 2. Check for the existence of the 'root' key
if "root" in data and isinstance(data['root'], dict):
print("PASS: Response contains a valid 'root' key.")
else:
print("FAIL: Response does not contain a valid 'root' key.")
# 3. Check for the existence and format of the 'visualization_url' key
if "visualization_url" in data and data["visualization_url"].endswith(".png"):
print(f"PASS: Response contains a valid 'visualization_url': {data['visualization_url']}")
else:
print("FAIL: Response does not contain a valid 'visualization_url'.")
except requests.exceptions.RequestException as e:
print(f"\n❌ TEST FAILED: Could not connect to the server.")
print(" Please make sure the backend service is running.")
print(f" Error details: {e}")
except json.JSONDecodeError:
print(f"\n❌ TEST FAILED: The server response was not valid JSON.")
print(f" Response text: {response.text}")
except Exception as e:
print(f"\n❌ TEST FAILED: An unexpected error occurred: {e}")
if __name__ == "__main__":
test_generate_plan()

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
from atexit import register
import os
import sys
import logging
import asyncio
from langchain.memory import ConversationBufferWindowMemory
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from pydantic import SecretStr
import json
from pathlib import Path
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[1] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
#添加到搜索路径
sys.path.append(str(project_root))
#查看系统环境
print("sys path:",sys.path)
from tools.core.common_functions import read_json_file
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
class MCP_Clients:
def __init__(self):
"""
初始化MCP_Clients
"""
self.multi_server_mcp_client = None
def parse_mcp_client_config(self,mcp_clients_config_json_file:str):
"""
读取mcp_client_config的JSON文件并返回解析后的数据
参数:
mcp_clients_config_json_file: mcp_clients配置信息的JSON文件的路径
返回:
解析后的JSON数据通常是字典或列表如果出错则返回None
"""
try:
# 使用with语句打开文件确保文件会被正确关闭
with open(mcp_clients_config_json_file, 'r', encoding='utf-8') as file:
# 加载并解析JSON数据
json_data = json.load(file)
print(f"成功读取JSON文件: {mcp_clients_config_json_file}")
return json_data
except FileNotFoundError:
print(f"错误: 找不到文件 '{mcp_clients_config_json_file}'")
except json.JSONDecodeError:
print(f"错误: 文件 '{mcp_clients_config_json_file}' 不是有效的JSON格式")
except PermissionError:
print(f"错误: 没有权限读取文件 '{mcp_clients_config_json_file}'")
except Exception as e:
print(f"读取JSON文件时发生错误: {str(e)}")
return None
def register_mcp_client(self,mcp_clients_config_json):
"""
注册具体的mcp_client
参数:
mcp_clients_config_json: mcp_clients json格式的配置信息
返回:
注册成功返回True失败返回False
"""
mcp_clients_len = len(mcp_clients_config_json)
success_register_count = 0
for client_config in mcp_clients_config_json:
mcp_client_key_name = list(client_config.keys())[0]
if "MultiServerMCPClient" == mcp_client_key_name:
self.multi_server_mcp_client = MultiServerMCPClient(client_config[mcp_client_key_name])
if self.multi_server_mcp_client:
logger.info("MultiServerMCPClient register success!")
success_register_count=success_register_count + 1
else:
logger.info("mcp_client_key_name is not known!")
return (success_register_count,mcp_clients_len)
async def main():
mcp_clients_config_json_file = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/tools/mcp_clients_config.json"
mcp_client = MCP_Clients()
mcp_client_config_json = mcp_client.parse_mcp_client_config(mcp_clients_config_json_file)
if not mcp_client_config_json:
logger.error("no json data.")
(sucess_register_count,clients_len) = mcp_client.register_mcp_client(mcp_client_config_json)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n👋 程序已退出")
except Exception as e:
print(f"❌ 程序异常退出: {e}")

View File

@@ -0,0 +1,26 @@
[
{
"MultiServerMCPClient": {
"terminal": {
"command": "python3",
"args": ["./mcp_servers/terminal_server.py"],
"transport": "stdio"
},
"app_analysis": {
"command": "python3",
"args": ["./mcp_servers/application_analysis_server.py"],
"transport": "stdio"
},
"browser": {
"command": "python3",
"args": ["./mcp_servers/browser_control_server.py"],
"transport": "stdio"
},
"weather": {
"command": "python3",
"args": ["./mcp_servers/weather_server.py"],
"transport": "stdio"
}
}
}
]

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python3
import asyncio
import sys
from langchain.memory import ConversationBufferWindowMemory
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from pydantic import SecretStr
import logging
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
class MCP_Servers:
pass

View File

@@ -0,0 +1,206 @@
# 基于LangChain和Llama.cpp的本地RAG实现
# 安装依赖: pip install langchain llama-cpp-python chromadb pypdf sentence-transformers
import os
from langchain.document_loaders import PyPDFLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.llms import LlamaCpp
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from llama_cpp_embeddings import LlamaCppEmbeddings
class LocalRAGSystem:
def __init__(self,
data_dir="./documents",
db_dir="./local_chroma_db",
model_path="./models/llama-2-7b-chat.Q4_K_M.gguf",
embedding_model_name="all-MiniLM-L6-v2"):
"""
初始化本地RAG系统
:param data_dir: 文档存放目录
:param db_dir: 向量数据库存放目录
:param model_path: 本地LLM模型路径(GGUF格式)
:param embedding_model_name: 用于生成嵌入的模型名称
"""
self.data_dir = data_dir
self.db_dir = db_dir
self.model_path = model_path
self.embedding_model_name = embedding_model_name
# 初始化组件
self.embeddings = None
self.vector_db = None
self.llm = None
self.qa_chain = None
# 创建必要的目录
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.db_dir, exist_ok=True)
def load_and_process_documents(self):
"""加载并处理文档"""
# 加载PDF文档
loader = DirectoryLoader(
path=self.data_dir,
glob="*.pdf",
loader_cls=PyPDFLoader
)
documents = loader.load()
print(f"加载了 {len(documents)} 个PDF文档")
# 分割文档
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""]
)
splits = text_splitter.split_documents(documents)
print(f"文档分割完成,得到 {len(splits)} 个片段")
return splits
def initialize_embeddings(self,embeddings_type : str):
"""初始化嵌入模型"""
if embeddings_type == "HF":
self.embeddings = HuggingFaceEmbeddings(
model_name=self.embedding_model_name,
model_kwargs={'device': 'cpu'}, # 可以改为'cuda'如果有GPU
encode_kwargs={'normalize_embeddings': True}
)
if embeddings_type == "llama_cpp":
self.embeddings = LlamaCppEmbeddings(
model_path=self.embedding_model_name,
n_threads=4 # 根据 CPU 核心数调整(如 8 核心设为 4 或 8
)
print(f"已加载嵌入模型: {self.embedding_model_name}")
return self.embeddings
def create_vector_database(self, documents, overwrite=False):
"""创建或加载向量数据库"""
if not self.embeddings:
self.initialize_embeddings()
# 检查是否需要覆盖现有数据库
if os.path.exists(self.db_dir) and not overwrite:
print(f"加载现有向量数据库: {self.db_dir}")
self.vector_db = Chroma(
persist_directory=self.db_dir,
embedding_function=self.embeddings
)
else:
print(f"创建新的向量数据库: {self.db_dir}")
self.vector_db = Chroma.from_documents(
documents=documents,
embedding=self.embeddings,
persist_directory=self.db_dir
)
self.vector_db.persist()
return self.vector_db
def initialize_llm(self):
"""初始化本地LLM (使用Llama.cpp)"""
# 检查模型文件是否存在
if not os.path.exists(self.model_path):
raise FileNotFoundError(f"模型文件未找到: {self.model_path}\n请下载GGUF格式的LLM模型并更新路径")
# 回调管理器用于流式输出
callback_manager = CallbackManager([StreamingStdOutCallbackHandler()])
# 初始化Llama.cpp LLM
self.llm = LlamaCpp(
model_path=self.model_path,
callback_manager=callback_manager,
verbose=False, # 设为True可查看调试信息
n_ctx=2048, # 上下文窗口大小
n_threads=4, # 线程数根据CPU核心数调整
n_gpu_layers=0 # GPU加速层数设为0表示仅用CPU有GPU可适当增加
)
print(f"已加载本地LLM模型: {os.path.basename(self.model_path)}")
return self.llm
def create_qa_chain(self):
"""创建检索增强问答链"""
if not self.vector_db:
raise ValueError("请先创建向量数据库")
if not self.llm:
self.initialize_llm()
# 定义提示词模板
prompt_template = """使用以下提供的上下文信息来回答用户的问题。
如果你不知道答案,直接说不知道,不要编造。回答要简洁明了。
上下文:
{context}
问题:
{question}
回答:
"""
# 创建提示词
prompt = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
# 创建检索式问答链
self.qa_chain = RetrievalQA.from_chain_type(
llm=self.llm,
chain_type="stuff",
retriever=self.vector_db.as_retriever(
search_kwargs={"k": 3} # 检索最相关的3个文档片段
),
chain_type_kwargs={"prompt": prompt},
return_source_documents=True # 返回用于回答的源文档
)
return self.qa_chain
def answer_query(self, query):
"""回答用户查询"""
if not self.qa_chain:
self.create_qa_chain()
print(f"\n查询: {query}")
print("回答: ", end="") # 保持与流式输出在同一行
result = self.qa_chain({"query": query})
# 输出源文档信息
print("\n\n参考文档:")
for i, doc in enumerate(result["source_documents"], 1):
print(f"文档 {i}: {doc.metadata.get('source', '未知来源')}, 页码: {doc.metadata.get('page', '未知')}")
return result
if __name__ == "__main__":
# 初始化RAG系统
rag_system = LocalRAGSystem(
model_path="./models/llama-2-7b-chat.Q4_K_M.gguf", # 替换为你的模型路径
data_dir="./documents", # 替换为你的文档目录
db_dir="./local_chroma_db"
)
# 处理文档并创建向量数据库 (首次运行需要,之后可注释掉)
documents = rag_system.load_and_process_documents()
rag_system.create_vector_database(documents)
# 初始化LLM和问答链
rag_system.initialize_llm()
rag_system.create_qa_chain()
# 示例查询
while True:
user_query = input("\n请输入你的问题 (输入 'exit' 退出): ")
if user_query.lower() == 'exit':
break
rag_system.answer_query(user_query)

View File

@@ -0,0 +1,286 @@
import os
from typing import List, Dict, Any, Optional, Tuple, Union
from io import BytesIO
import numpy as np
from llama_cpp import Llama
from langchain_core.embeddings import Embeddings
from pydantic import BaseModel
import base64
from PIL import Image
import logging
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
class LlamaCppCustomEmbeddings(Embeddings):
"""
自定义LlamaCpp嵌入类绕开langchain_community封装直接控制底层参数
适配LangChain的Embeddings接口需实现embed_query和embed_documents
"""
def __init__(
self,
model_path: str,
n_ctx: int = 512,
n_threads: int = 4,
n_gpu_layers: int = 0,
n_seq_max: int = 128, # 底层支持的批量序列数参数
n_threads_batch = None,
flash_attn = False,
verbose: bool = False
):
try:
# 直接初始化llama-cpp-python的Llama类传递所有底层参数
self.model = Llama(
model_path=model_path,
n_ctx=n_ctx, # 上下文长度(匹配文档长度)
n_threads=n_threads, # CPU线程数
n_gpu_layers=n_gpu_layers, # GPU层数量RTX 5090可设20+
n_seq_max=n_seq_max, # 允许批量处理的最大序列数解决seq_id超限
n_threads_batch = n_threads_batch,
flash_attn= flash_attn,
verbose=verbose,
embedding=True, # 强制启用嵌入模式(底层关键参数)
chat_format="none" # 禁用聊天模板(嵌入无需生成式格式)
)
# 验证模型是否正常加载(获取向量维度,确保嵌入功能可用)
# test_emb = self.embed_query("测试嵌入")
# self.embedding_dim = len(test_emb)
# logger.info(f"✅ 自定义嵌入模型加载成功!向量维度:{self.embedding_dim}n_seq_max{n_seq_max}")
except Exception as e:
logger.error(f"❌ llamacpp嵌入模型初始化失败: {str(e)}")
return None
def _validate_embedding_support(self) -> None:
"""验证模型是否支持嵌入功能,避免加载非嵌入模型"""
model_meta = self.model.metadata or {}
# 嵌入模型通常包含"embedding"关键字或特定架构如bge、mistral-embed
if "embedding" not in model_meta.get("general.name", "").lower() and \
model_meta.get("general.architecture", "") not in ["bge", "mistral-embed"]:
logger.warning(
"当前模型可能不是专用嵌入模型,可能导致嵌入结果异常!"
"建议使用bge-small-zh-v1.5-GGUF或mistral-embed-v0.1-GGUF"
)
def embed_documents(self, texts: List[str]) -> List[List[float]]:
"""Embed a list of documents using the Llama model.
Args:
texts: The list of texts to embed.
Returns:
List of embeddings, one for each text.
"""
# embeddings = self.model.create_embedding(texts)
embeddings = [self.model.embed(text) for text in texts]
final_embeddings = []
# for e in embeddings["data"]:
# try:
# if isinstance(e["embedding"][0], list):
# for data in e["embedding"]:
# final_embeddings.append(list(map(float, data)))
# else:
# final_embeddings.append(list(map(float, e["embedding"])))
# except (IndexError, TypeError):
# final_embeddings.append(list(map(float, e["embedding"])))
for e in embeddings:
try:
if isinstance(e[0], list):
for data in e:
final_embeddings.append(list(map(float, data)))
else:
final_embeddings.append(list(map(float, e)))
except (IndexError, TypeError):
final_embeddings.append(list(map(float, e["embedding"])))
return final_embeddings
def embed_query(self, text: str) -> List[float]:
"""Embed a query using the Llama model.
Args:
text: The text to embed.
Returns:
Embeddings for the text.
"""
embedding = self.model.embed(text)
if embedding and isinstance(embedding, list) and isinstance(embedding[0], list):
return list(map(float, embedding[0]))
else:
return list(map(float, embedding))
def embed_query2(self, text: str) -> list[float]:
"""实现单句查询嵌入LangChain必选方法"""
# 底层llama.embed返回字典key为"embedding"
return self.model.embed(text)#["embedding"]
def embed_documents2(self, texts: list[str]) -> list[list[float]]:
"""实现多文档批量嵌入LangChain必选方法优化批量性能"""
# 直接调用底层批量嵌入接口避免循环调用embed_query提升速度
# self.llm.embed(text)["embedding"]
return [self.model.embed(text) for text in texts]
def embed_documents1(self, texts: list[str]) -> list[list[float]]:
"""实现多文档批量嵌入LangChain必选方法优化批量性能"""
# 直接调用底层批量嵌入接口避免循环调用embed_query提升速度
# self.llm.embed(text)["embedding"]
# return [self.model.embed(text) for text in texts]
return self.model.embed(texts)
def embed_image(self, image: Union[Image.Image, str],n_ctx_size) -> List[float]:
"""为图像生成嵌入向量"""
if isinstance(image, Image.Image):
# 将图像转换为base64编码
buffer = BytesIO()
image.save(buffer, format="PNG")
image_base64 = base64.b64encode(buffer.getvalue()).decode()
else:
image_base64 = image # 假设是base64字符串
# 构造图像嵌入提示
prompt = f"Embed this image: data:image/png;base64,{image_base64}"
if len(prompt) > n_ctx_size:
raise ValueError("图像编码过长,超过模型上下文限制")
return self.model.create_embedding(prompt)["data"][0]["embedding"]
def normalize_embeddings(self, embeddings: list[np.ndarray]) -> list[np.ndarray]:
"""
嵌入向量归一化(可选但推荐:提升后续检索的相似度计算精度)
:param embeddings: 原始嵌入向量列表
:return: 归一化后的嵌入向量列表
"""
normalized = []
for emb in embeddings:
norm = np.linalg.norm(emb) # 计算向量的 L2 范数
if norm == 0:
normalized.append(emb)
else:
normalized.append(emb / norm) # 归一化到单位向量
return normalized
# class LlamaCppEmbeddings(BaseModel,Embeddings):
class LlamaCppEmbeddings:
"""
基于llama-cpp-python实现的嵌入工具类
支持GGUF格式嵌入模型本地运行兼容CPU/GPU加速
"""
def __init__(
self,
model_path: str,
n_ctx: int = 512,
n_gpu_layers: int = 0,
n_threads: Optional[int] = None,
n_batch=512,
verbose: bool = False,
embedding: bool = True # 强制启用嵌入模式(关键参数)
):
"""
初始化参数说明:
:param model_path: GGUF格式嵌入模型的本地路径必填
:param n_ctx: 模型上下文窗口大小(需 ≤ 模型的context_length默认512
:param n_gpu_layers: 分配到GPU的层数量0=全CPU≥1=GPU加速根据显存调整
:param n_threads: CPU推理线程数默认=CPU核心数//2平衡性能与开销
:param verbose: 是否打印模型加载日志调试用True上线用False
:param embedding: 必须设为True启用嵌入模式否则模型以生成模式运行
:param n_batch # 批量处理大小加速embed_documents
"""
# 1. 校验模型路径
if not os.path.exists(model_path):
raise FileNotFoundError(f"模型文件不存在:{model_path}")
if not model_path.endswith(".gguf"):
logger.warning("模型文件后缀非.gguf可能不是llama.cpp兼容格式")
# 2. 初始化默认参数
self.n_threads = n_threads or (os.cpu_count() // 2) if os.cpu_count() else 4
# 3. 加载llama.cpp模型核心步骤
self.model = Llama(
model_path=model_path,
n_ctx=n_ctx,
n_gpu_layers=n_gpu_layers,
n_threads=self.n_threads,
n_batch=n_batch, # 批量处理大小加速embed_documents
verbose=verbose,
embedding=embedding # 强制启用嵌入模式,否则无法生成向量
)
# 4. 验证模型是否支持嵌入(通过元数据判断)
# self._validate_embedding_support()
def _validate_embedding_support(self) -> None:
"""验证模型是否支持嵌入功能,避免加载非嵌入模型"""
model_meta = self.model.metadata or {}
# 嵌入模型通常包含"embedding"关键字或特定架构如bge、mistral-embed
if "embedding" not in model_meta.get("general.name", "").lower() and \
model_meta.get("general.architecture", "") not in ["bge", "mistral-embed"]:
logger.warning(
"当前模型可能不是专用嵌入模型,可能导致嵌入结果异常!"
"建议使用bge-small-zh-v1.5-GGUF或mistral-embed-v0.1-GGUF"
)
def embed_document(self, document: str) -> np.ndarray:
"""
生成单条文本的嵌入向量
:param document: 输入文本(建议预处理:去除多余空格、换行)
:return: 384 维嵌入向量numpy 数组)
"""
# 预处理文本(避免特殊字符影响嵌入质量)
document = document.strip().replace("\n", " ").replace("\r", "")
# 调用 Llama.cpp 生成嵌入(返回 list需转为 numpy 数组)
embedding = self.model.create_embedding(document)["data"][0]["embedding"]
return np.array(embedding, dtype=np.float32)
def embed_documents(self, documents: list[str]) -> list[np.ndarray]:
"""
批量生成多条文本的嵌入向量(比单条调用更高效)
:param documents: 文本列表
:return: 嵌入向量列表(每个元素为 384 维 numpy 数组)
"""
return [self.embed_document(document) for document in documents]
def normalize_embeddings(self, embeddings: list[np.ndarray]) -> list[np.ndarray]:
"""
嵌入向量归一化(可选但推荐:提升后续检索的相似度计算精度)
:param embeddings: 原始嵌入向量列表
:return: 归一化后的嵌入向量列表
"""
normalized = []
for emb in embeddings:
norm = np.linalg.norm(emb) # 计算向量的 L2 范数
if norm == 0:
normalized.append(emb)
else:
normalized.append(emb / norm) # 归一化到单位向量
return normalized
def embed_query(self, text: str) -> list[float]:
"""处理单个查询文本的嵌入"""
return self.embed_documents([text])
def embed_image(self, image: Union[Image.Image, str],n_ctx_size) -> List[float]:
"""为图像生成嵌入向量"""
if isinstance(image, Image.Image):
# 将图像转换为base64编码
buffer = BytesIO()
image.save(buffer, format="PNG")
image_base64 = base64.b64encode(buffer.getvalue()).decode()
else:
image_base64 = image # 假设是base64字符串
# 构造图像嵌入提示
prompt = f"Embed this image: data:image/png;base64,{image_base64}"
if len(prompt) > n_ctx_size:
raise ValueError("图像编码过长,超过模型上下文限制")
return self.llm.create_embedding(prompt)["data"][0]["embedding"]

View File

@@ -0,0 +1,308 @@
# 基于LangChain和Llama.cpp的本地RAG实现
# 安装依赖: pip install langchain llama-cpp-python chromadb pypdf sentence-transformers
import os
import sys
from pathlib import Path
from typing import List, Dict, Any, Optional, Tuple, Union
import logging
# from langchain.document_loaders import PyPDFLoader, DirectoryLoader,JSONLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# from langchain.embeddings import HuggingFaceEmbeddings
# from langchain.vectorstores import Chroma
# from langchain.chains import RetrievalQA
# from langchain.prompts import PromptTemplate
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain.schema import Document
# from langchain_core.embeddings import Embeddings
from langchain_community.document_loaders import PyPDFLoader,DirectoryLoader,JSONLoader
from langchain_community.embeddings import HuggingFaceEmbeddings, LlamaCppEmbeddings
import json # 导入json模块
from unstructured.partition.auto import partition
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[5] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
# print("sys_path: ",sys.path)
# # 从环境变量中获取项目根目录
# project_root = os.environ.get("PROJECT_ROOT")
# if project_root:
# # 将项目根目录添加到搜索路径
# sys.path.append(project_root)
# print(f"已添加项目路径:{project_root}")
# else:
# raise ValueError("请先设置 PROJECT_ROOT 环境变量")
# 添加到搜索路径,如果路径找不到则加入
if project_root not in sys.path:
sys.path.append(str(project_root))
# print("sys_path: ",sys.path)
from tools.memory_mag.rag.llama_cpp_embeddings import LlamaCppCustomEmbeddings
from tools.memory_mag.rag.rag import set_embeddings, build_vector_database
# from llama_cpp import Llama
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
#加载json文件
def load_json_documents(json_dir):
"""加载JSON文件并提取内容"""
documents = []
# 遍历目录中的所有JSON文件
for filename in os.listdir(json_dir):
if filename.endswith('.json'):
file_path = os.path.join(json_dir, filename)
try:
# 使用JSONLoader加载文件
# jq_schema参数用于指定要提取的JSON字段这里提取所有字段
loader = JSONLoader(
file_path=file_path,
jq_schema='.', # 提取整个JSON内容
text_content=False # 保留JSON结构
)
# 加载文档
loaded_docs = loader.load()
print(f"成功加载JSON文件: {filename}, 包含 {len(loaded_docs)} 个文档对象")
# 为每个文档添加文件名元数据
for doc in loaded_docs:
doc.metadata['source'] = filename
documents.append(doc)
except Exception as e:
print(f"加载JSON文件 {filename} 时出错: {str(e)}")
if not documents:
print("警告: 未加载到任何JSON文档请检查json_dir目录")
else:
print(f"总共加载 {len(documents)} 个JSON文档对象")
return documents
#加载ndjson文件
def load_ndjson_documents(file_path: str) -> list[str]:
"""
加载 NDJSON 文件,提取每行 JSON 中的关键文本字段
Args:
file_path: NDJSON 文件路径(如 "data/docs.ndjson"
Returns:
提取的文本列表(每个元素对应一行 NDJSON 的关键内容)
"""
documents = []
if not os.path.exists(file_path):
raise FileNotFoundError(f"NDJSON 文件不存在:{file_path}")
with open(file_path, "r", encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line: # 跳过空行
continue
try:
# 解析每行 JSON需根据你的 NDJSON 结构调整字段名)
json_obj = json.loads(line)
# 示例:提取 "content" 字段(若你的字段是 "text",则改为 json_obj["text"]
if "content" not in json_obj:
print(f"警告:第 {line_num} 行 NDJSON 缺少 'content' 字段,跳过")
continue
text = json_obj["content"].strip()
if text: # 跳过空文本
documents.append(text)
except json.JSONDecodeError as e:
print(f"警告:第 {line_num} 行 NDJSON 解析失败:{e},跳过")
return documents
#加载文档
def load_documents(file_mame: str):
"""从知识库目录加载所有文档并进行切分"""
documents = []
file_path = Path(file_mame)
# 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(Document(
page_content=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(Document(
page_content=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(Document(
page_content=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):
# print(f"src text: {record['text']}")
record['text']=text_clean_process(record['text'])
# print(f"processed text: {record['text']}")
# logger.info(f"该句文本token数量:{len(record['text'])}")
documents.append(Document(
page_content=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(Document(
page_content=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 process_and_split_documents(self, documents):
"""处理并分割文档"""
# 对于JSON数据我们需要将其转换为字符串并适当分割
# 首先将每个JSON文档转换为格式化的字符串
for doc in documents:
# 如果内容是字典转换为格式化的JSON字符串
if isinstance(doc.page_content, dict):
doc.page_content = json.dumps(doc.page_content, indent=2, ensure_ascii=False)
# 创建文本分割器考虑JSON结构特点设置分割参数
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=100,
separators=["}\n", "},\n", "\n\n", "\n", " ", ""] # 优先按JSON结构分割
)
chunks = text_splitter.split_documents(documents)
print(f"文档分割完成,得到 {len(chunks)} 个片段")
return chunks
#文本清洗
def text_clean_process(text:str):
# 增强文本清洗
text = text.strip().replace("\n", " ").replace("\r", "").replace("\t", " ")
# 移除多余空格和非UTF-8字符
text = " ".join(text.split())
text = text.encode("utf-8", errors="ignore").decode("utf-8")
return text
def build_osm_map_knowledge_base(
osm_map_file,
embedding_model_path,
embedding_type:str,
model_config:Optional[dict[str,Any]],
persist_directory,
collection_name =None
):
"""创建osm_map向量数据库确保嵌入函数有效"""
## 1. 加载文档已处理文本长度单条≈100token < 512
docs_to_ingest = load_documents(osm_map_file)
if not docs_to_ingest:
logging.warning("⚠️ 在知识库中未找到可处理的文档。")
return None
## 2. 初始化嵌入模型(使用自定义类)
embeddings = set_embeddings(embedding_model_path=embedding_model_path,
embedding_type=embedding_type,
model_config=model_config)
# 关键:检查嵌入模型是否初始化成功
if embeddings is None:
logger.error("❌ 嵌入模型未初始化,终止向量库构建")
return None
return build_vector_database(
documents=docs_to_ingest,
persist_directory=persist_directory,
embeddings=embeddings,
collection_name=collection_name)
if __name__ == "__main__":
osm_map_file = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/src_data/export.ndjson"
persist_directory = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map_4"
# n_threads = 8 # 建议设为CPU核心数的50%-80%如16核设8-12
# 调整GPU层数为20加速模型加载和嵌入生成
# "llamacpp_embeddings" /"huggingFace_embeddings"
embedding_type = "huggingFace_embeddings"
model_config_llamacpp={
"n_ctx":512,
"n_threads":4,
"n_gpu_layers":36,
"n_seq_max":256,
"n_threads_batch":None,
"flash_attn":True,
"verbose":False
}
model_config_huggingFace ={
"model_kwargs": {
"device": "cuda"
},
"encode_kwargs":{
"normalize_embeddings": True
}
}
if "llamacpp_embeddings" == embedding_type:
embedding_model_path = "/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen/Qwen3-Embedding-4B-GGUF/Qwen3-Embedding-4B-f16.gguf"
model_config=model_config_llamacpp
elif "huggingFace_embeddings" == embedding_type:
embedding_model_path="/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen/Qwen3-Embedding-4B"
model_config=model_config_huggingFace
build_osm_map_knowledge_base(
osm_map_file=osm_map_file,
embedding_model_path=embedding_model_path,
embedding_type=embedding_type,
model_config=model_config,
persist_directory=persist_directory,
collection_name="osm_map_docs")

View File

@@ -0,0 +1,194 @@
# 该代码用于将本地知识库中的文档导入到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()

View File

@@ -0,0 +1,337 @@
import os
import sys
from pathlib import Path
from typing import List, Dict, Any, Optional, Tuple, Union
import logging
from langchain.document_loaders import TextLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.document_loaders import (
TextLoader, UnstructuredFileLoader, PDFMinerLoader,
UnstructuredMarkdownLoader, Docx2txtLoader
)
from langchain_community.embeddings import HuggingFaceEmbeddings, LlamaCppEmbeddings
# 获取当前脚本的路径Path 对象)
current_script = Path(__file__).resolve()
# 向上两级找到项目根目录(根据实际结构调整层级)
project_root = current_script.parents[3] # parents[0] 是当前目录parents[1] 是父目录,以此类推
print("project_root: ",project_root)
# # 从环境变量中获取项目根目录
# project_root = os.environ.get("PROJECT_ROOT")
# if project_root:
# # 将项目根目录添加到搜索路径
# sys.path.append(project_root)
# print(f"已添加项目路径:{project_root}")
# else:
# raise ValueError("请先设置 PROJECT_ROOT 环境变量")
# 添加到搜索路径,如果该路径不存在则加入
# str(project_root)
if project_root not in sys.path:
sys.path.append(str(project_root))
from tools.memory_mag.rag.llama_cpp_embeddings import LlamaCppCustomEmbeddings
# 配置日志记录器
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
#设置huggingFace embedding模型
def set_embeddings_huggingFace(
model_path: str,
model_kwargs:Dict[str, Any]={'device': 'cpu'},
encode_kwargs:Dict[str, Any]={'normalize_embeddings': True}
):
try:
# 改用自定义嵌入类,传递底层所需参数
embeddings = HuggingFaceEmbeddings(
model_name=model_path,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
return embeddings
except Exception as e:
logger.error(f"❌ HuggingFace嵌入模型初始化失败: {str(e)}")
return None
#设置llamacpp embedding模型
def set_embeddings_llamacpp(
model_path: str,
n_ctx: int = 512,
n_threads: int = 4,
n_gpu_layers: int = 0,
n_seq_max: int = 128,
n_threads_batch = 4,
flash_attn = True,
verbose: bool = False
):
try:
# 改用自定义嵌入类,传递底层所需参数
embeddings = LlamaCppCustomEmbeddings(
model_path=model_path,
n_ctx=n_ctx,
n_threads=n_threads,
n_gpu_layers=n_gpu_layers,
n_seq_max=n_seq_max,
n_threads_batch = n_threads_batch,
flash_attn = flash_attn,
verbose=verbose
)
return embeddings
except Exception as e:
logger.error(f"❌ llamacpp嵌入模型初始化失败: {str(e)}")
return None
#设置embeddings模型
def set_embeddings(embedding_model_path,
embedding_type:str,
model_config:Optional[dict[str,Any]]):
if "llamacpp_embeddings" == embedding_type:
embeddings = set_embeddings_llamacpp(
model_path=embedding_model_path,
n_ctx=model_config["n_ctx"],
n_threads=model_config["n_threads"],
n_gpu_layers=model_config["n_gpu_layers"], # RTX 5090建议设20充分利用GPU
n_seq_max=model_config["n_seq_max"],
n_threads_batch = model_config["n_threads_batch"],
flash_attn = model_config["flash_attn"],
verbose=model_config["verbose"]
)
elif "huggingFace_embeddings" == embedding_type:
embeddings = set_embeddings_huggingFace(
model_path=embedding_model_path,
model_kwargs=model_config["model_kwargs"],
encode_kwargs = model_config["encode_kwargs"]
)
else:
logger.error("unknown embedding type!")
embeddings = None
# if embeddings is not None:
# test_texts=["测试嵌入","你好"]
# test_emb = embeddings.embed_query(test_texts)
# embedding_dim = len(test_emb)
# logger.info(f"✅ 自定义嵌入模型加载成功!向量维度:{embedding_dim}")
return embeddings
#构建向量数据库
def build_vector_database(documents,embeddings,persist_directory,collection_name):
try:
os.makedirs(name=persist_directory,
exist_ok=True)
print(f"创建新的向量数据库: {persist_directory}")
vector_db = Chroma.from_documents(
documents=documents,
embedding=embeddings,
persist_directory=persist_directory,
collection_name=collection_name
)
vector_db.persist()
print(f"Chroma 已持久化到: {persist_directory}collection_name={collection_name}")
return vector_db
except Exception as e:
logger.error(f"vector_database构建失败: {str(e)}")
return None
#加载向量数据库
def load_vector_database(embeddings,
persist_directory:str,
collection_name:str):
"""加载已存在的向量数据库"""
try:
if os.path.exists(path=persist_directory):
vector_db = Chroma(
persist_directory=persist_directory,
embedding_function=embeddings,
collection_name=collection_name
)
print(f"已加载向量数据库 from {persist_directory}")
return vector_db
else:
return None
except Exception as e:
logger.error(f"vector_database加载失败: {str(e)}")
return None
#设置文本分割器
def set_text_splitter(self,
chunk_size: int = 500,
chunk_overlap: int = 50,
separators: Optional[List]=["\n\n", "\n", " ", ""]):
try:
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
separators=separators
)
return text_splitter
except Exception as e:
logger.error(f"text_splitter初始化失败: {str(e)}")
return None
def set_document_loaders(self):
try:
document_loaders = {
'.txt': TextLoader,
'.pdf': PDFMinerLoader,
'.md': UnstructuredMarkdownLoader,
'.docx': Docx2txtLoader,
# 可以添加更多文档类型
}
return document_loaders
except Exception as e:
logger.error(f"document_loaders初始化失败: {str(e)}")
return None
def retrieve_relevant_info(vectorstore,
query: str,
k: int = 3,
score_threshold: float = 0.2
) -> List[Dict[str, Any]]:
"""
检索与查询相关的信息
参数:
query: 查询文本
k: 最多返回的结果数量
score_threshold: 相关性分数阈值
返回:
包含相关文档内容和分数的列表
"""
try:
# 执行相似性搜索,返回带分数的结果
docs_and_scores = vectorstore.similarity_search_with_score(query, k=k)
# 过滤并格式化结果
results = []
for doc, score in docs_and_scores:
if score < score_threshold: # 分数越低表示越相似
results.append({
"content": doc.page_content,
"metadata": doc.metadata,
"similarity_score": float(score)
})
logger.info(f"检索到 {len(results)} 条相关信息 (k={k}, 阈值={score_threshold})")
return results
except Exception as e:
logger.error(f"检索相关信息失败: {str(e)}")
return []
def get_retriever(vectorstore,search_kwargs: Dict[str, Any] = None) -> Any:
"""获取检索器对象用于LangChain的RetrievalQA链"""
if search_kwargs is None:
search_kwargs = {"k": 3}
return vectorstore.as_retriever(search_kwargs=search_kwargs)
def test_load_vdb(embedding_model_path,
embedding_type,
model_config,
persist_directory,
collection_name):
#1. 初始化嵌入模型(使用自定义类)
embeddings = set_embeddings(embedding_model_path=embedding_model_path,
embedding_type=embedding_type,
model_config=model_config)
vector_db= load_vector_database(embeddings=embeddings,
persist_directory=persist_directory,
collection_name=collection_name)
logger.info("load_vdb finished")
return vector_db
def main():
persist_directory = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map_4"
embedding_type = "huggingFace_embeddings"
model_config_llamacpp={
"n_ctx":512,
"n_threads":4,
"n_gpu_layers":36,
"n_seq_max":256,
"n_threads_batch":None,
"flash_attn":True,
"verbose":False
}
model_config_huggingFace ={
"model_kwargs": {
"device": "cuda"
},
"encode_kwargs":{
"normalize_embeddings": True
}
}
if "llamacpp_embeddings" == embedding_type:
embedding_model_path = "/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen/Qwen3-Embedding-4B-GGUF/Qwen3-Embedding-4B-f16.gguf"
model_config=model_config_llamacpp
elif "huggingFace_embeddings" == embedding_type:
embedding_model_path="/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Qwen/Qwen3-Embedding-4B"
model_config=model_config_huggingFace
vector_db = test_load_vdb(embedding_model_path=embedding_model_path,
embedding_type=embedding_type,
model_config=model_config,
persist_directory=persist_directory,
collection_name="osm_map_docs")
# query = "学生宿舍10幢"
# retrieve_result = retrieve_relevant_info(query=query,
# vectorstore=vector_db,
# k=3,
# score_threshold=0.2)
# retriever = get_retriever(vectorstore=vector_db,search_kwargs={"k": 3})
# results = retriever.get_relevant_documents(query)
try:
# 创建检索器
retriever = vector_db.as_retriever(search_kwargs={"k": 3})
# 执行查询
query = "基础实验楼"
results = retriever.get_relevant_documents(query)
if results:
logger.info(f"查询 '{query}' 返回了 {len(results)} 个结果:")
for i, doc in enumerate(results):
logger.info(f"\n结果 {i+1}:")
logger.info(f"内容: {doc.page_content[:100]}...")
logger.info(f"来源: {doc.metadata.get('source', '未知')}")
else:
logger.warning("查询未返回任何结果")
except Exception as e:
logger.error(f"查询测试失败: {str(e)}")
print("main() finished!")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,249 @@
import os
import sqlite3
from pprint import pprint
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import LlamaCppEmbeddings
import chromadb
def read_aqlite3():
# 连接到 SQLite 数据库(如果数据库不存在,会自动创建)
# 请替换为你的数据库文件路径
db_path = 'src/Model/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map4/chroma.sqlite3'
conn = sqlite3.connect(db_path)
# 创建一个游标对象,用于执行 SQL 查询
cursor = conn.cursor()
# 执行查询,获取所有表名
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
# 获取所有表的名称
tables = cursor.fetchall()
print("数据库中的所有表:")
for table in tables:
print(table[0]) # 打印每个表的名称
# 假设我们选择某个表进行查询,获取表的前 10 行数据
table_name = 'your_table_name' # 替换为你实际的表名
cursor.execute(f"SELECT * FROM {table_name} LIMIT 10;")
# 获取查询结果
rows = cursor.fetchall()
print(f"{table_name} 表的前 10 行数据:")
for row in rows:
print(row)
# 完成后关闭游标和连接
cursor.close()
conn.close()
def read_chromdb():
# 加载现有的 Chroma 数据库
# 替换为实际的路径
db_dir = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map1"
# vector_db = Chroma(persist_directory=db_dir)
# # 获取所有文档
# documents = vector_db.get_documents()
# # 输出前 5 个文档的信息
# print(f"共有 {len(documents)} 个文档存储在 Chroma 数据库中。")
# for i, doc in enumerate(documents[:5]):
# print(f"文档 {i + 1}: {doc.metadata['source']}") # 输出每个文档的源信息(文件名等)
# print(f"内容: {doc.page_content[:300]}...") # 打印文档的前 300 个字符
# print("-" * 50)
vector_db = Chroma(persist_directory=db_dir)
# # Retrieve all document IDs
document_ids = vector_db.get_all_ids() # Get all document IDs
# Retrieve all documents based on the IDs
documents = vector_db.get_by_ids(document_ids)
# Print out all documents' metadata and content
for i, doc in enumerate(documents, 1):
print(f"\nDocument {i} (Source: {doc.metadata.get('source', 'N/A')}):")
print(f" Content: {doc.page_content[:500]}...") # Show first 500 characters to avoid overflow
print(f" Metadata: {doc.metadata}")
class ChromaDataViewer:
def __init__(
self,
persist_directory: str,
embedding_model_path: str,
collection_name: str = "langchain",
n_ctx: int = 1024,
n_threads: int = 4
):
"""初始化Chroma向量数据库查看器"""
self.persist_directory = persist_directory
self.collection_name = collection_name
# 初始化与向量库匹配的嵌入模型(必须与入库时一致)
self.embeddings = LlamaCppEmbeddings(
model_path=embedding_model_path,
n_ctx=n_ctx,
n_threads=n_threads
# embedding=True # 启用嵌入模式
)
# 连接到现有向量库
self.vector_db = self._connect_to_chroma()
def _connect_to_chroma(self) -> Chroma:
"""连接到Chroma向量数据库"""
if not os.path.exists(self.persist_directory):
raise FileNotFoundError(f"向量库路径不存在:{self.persist_directory}")
try:
return Chroma(
persist_directory=self.persist_directory,
embedding_function=self.embeddings,
collection_name=self.collection_name
)
except Exception as e:
raise RuntimeError(f"连接向量库失败:{str(e)}")
def get_collection_info(self) -> dict:
"""获取向量库集合基本信息"""
# 通过底层Chroma客户端获取集合信息
collection = self.vector_db._collection
return {
"collection_name": self.collection_name,
"document_count": collection.count(), # 文档总数
"vector_dimension": collection.get(limit=1)["embeddings"][0].shape[0] if collection.count() > 0 else 0, # 向量维度
"persist_directory": self.persist_directory
}
def list_all_document_ids(self) -> list[str]:
"""获取所有文档的ID列表"""
return self.vector_db._collection.get()["ids"]
def get_documents_by_ids(self, ids: list[str] = None, limit: int = 5) -> list[dict]:
"""
获取指定ID的文档内容和元数据
:param ids: 文档ID列表None则获取前limit个
:param limit: 最大返回数量ids为None时生效
"""
if ids is None:
all_ids = self.list_all_document_ids()
ids = all_ids[:limit] # 取前limit个
result = self.vector_db._collection.get(ids=ids)
documents = []
for i in range(len(ids)):
# 匹配ID对应的文档内容和元数据
doc_idx = result["ids"].index(ids[i]) if ids[i] in result["ids"] else -1
if doc_idx == -1:
continue
documents.append({
"id": ids[i],
"content": result["documents"][doc_idx], # 文档文本内容
"metadata": result["metadatas"][doc_idx], # 元数据(如来源、类型)
"embedding_sample": result["embeddings"][doc_idx][:5] # 向量前5维示例
})
return documents
def search_similar_documents(self, query: str, k: int = 3) -> list[dict]:
"""检索与查询相似的文档(带相似度分数)"""
results = self.vector_db.similarity_search_with_score(query, k=k)
similar_docs = []
for doc, score in results:
similar_docs.append({
"content": doc.page_content,
"metadata": doc.metadata,
"similarity_score": score, # 相似度分数(越小越相似,取决于距离函数)
"id": doc.metadata.get("id", "未知ID")
})
return similar_docs
def read_chromdb2():
# 配置向量库路径和嵌入模型路径(替换为你的实际路径)
# 向量库存储目录
PERSIST_DIRECTORY = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map1/"
EMBEDDING_MODEL_PATH = "/home/ubuntu/Workspace/Projects/VLM_VLA/Models/Embedding-GGUF/all-MiniLM-L6-v2-GGUF/all-MiniLM-L6-v2.F16.gguf" # 与入库时相同的嵌入模型
COLLECTION_NAME = "drone_docs" # 集合名(与入库时一致)
# 初始化查看器
viewer = ChromaDataViewer(
persist_directory=PERSIST_DIRECTORY,
embedding_model_path=EMBEDDING_MODEL_PATH,
collection_name=COLLECTION_NAME,
n_threads=4
)
# 1. 查看集合基本信息
print("=== 向量库集合信息 ===")
pprint(viewer.get_collection_info())
print("\n" + "="*50 + "\n")
# 2. 查看前3个文档的详细信息内容、元数据、向量示例
print("=== 前3个文档详情 ===")
sample_docs = viewer.get_documents_by_ids(limit=3)
for i, doc in enumerate(sample_docs, 1):
print(f"\n--- 文档 {i} ---")
print(f"ID: {doc['id']}")
print(f"来源: {doc['metadata'].get('source', '未知')}")
print(f"内容预览: {doc['content'][:200]}...") # 显示前200字符
print(f"向量前5维: {doc['embedding_sample']}")
print("\n" + "="*50 + "\n")
# # 3. 检索相似文档(示例)
# test_query = "你的查询关键词" # 替换为你的查询
# print(f"=== 与 '{test_query}' 相似的文档 ===")
# similar_docs = viewer.search_similar_documents(query=test_query, k=2)
# for i, doc in enumerate(similar_docs, 1):
# print(f"\n--- 相似文档 {i} (分数: {doc['similarity_score']:.4f}) ---")
# print(f"来源: {doc['metadata'].get('source', '未知')}")
# print(f"内容预览: {doc['content'][:200]}...")
def read_chromadb_data(data_path,collection_name,show_data_count):
# ---------------------- 1. 连接ChromaDB并提取原数据 ----------------------
# 1.1 初始化Chroma客户端根据你的实际存储方式选择这里以持久化存储为例
client = chromadb.PersistentClient(path=data_path) # 替换为你的ChromaDB存储路径
#client = chromadb.PersistentClient(path="./osm_map1") # 替换为你的ChromaDB存储路径
# 1.2 获取目标集合Collection
#collection = client.get_collection(name="drone_docs") # 替换为你的集合名
collection = client.get_collection(name=collection_name) # 替换为你的集合名
print("all data len:",collection.count())
# breakpoint()
# cnt=10
data = collection.get(include=['embeddings', 'documents', 'metadatas'])
all_info={}
for i in range(show_data_count):
ids=data["ids"][i]
docs=data["documents"][i]
embedings=data["embeddings"][i]
metadata=data["metadatas"][i]
all_info["ids"]=ids
all_info["docs"]=docs
all_info["embeddings"]=embedings
all_info["metadata"]=metadata
print(all_info)
def main():
vector_data_path = "/home/ubuntu/Workspace/Projects/VLM_VLA/UAV_AI/src/Model/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map_4/"
read_chromadb_data(data_path=vector_data_path,
collection_name="osm_map_docs",
show_data_count=10)
print("main finished!")
if __name__ == "__main__":
main()

0
srv/.gitkeep Normal file
View File