AI_Agent first commit
This commit is contained in:
0
scripts/ai_agent/agents/.gitkeep
Normal file
0
scripts/ai_agent/agents/.gitkeep
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
scripts/ai_agent/agents/__pycache__/rag_manager.cpython-310.pyc
Normal file
BIN
scripts/ai_agent/agents/__pycache__/rag_manager.cpython-310.pyc
Normal file
Binary file not shown.
47
scripts/ai_agent/agents/mcp_manager.py
Normal file
47
scripts/ai_agent/agents/mcp_manager.py
Normal 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
|
||||
|
||||
|
||||
453
scripts/ai_agent/agents/memory_manager.py
Normal file
453
scripts/ai_agent/agents/memory_manager.py
Normal 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")
|
||||
|
||||
# 情况2:JSON内容是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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
454
scripts/ai_agent/agents/multimodal_large_model_agent.py
Normal file
454
scripts/ai_agent/agents/multimodal_large_model_agent.py
Normal 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("已清除所有对话上下文和记忆")
|
||||
|
||||
137
scripts/ai_agent/agents/multimodal_processor.py
Normal file
137
scripts/ai_agent/agents/multimodal_processor.py
Normal 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()
|
||||
}
|
||||
118
scripts/ai_agent/agents/prompt_manager.py
Normal file
118
scripts/ai_agent/agents/prompt_manager.py
Normal 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
|
||||
321
scripts/ai_agent/agents/rag_manager.py
Normal file
321
scripts/ai_agent/agents/rag_manager.py
Normal 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)
|
||||
70
scripts/ai_agent/ai_agent_requirements.txt
Normal file
70
scripts/ai_agent/ai_agent_requirements.txt
Normal 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
|
||||
|
||||
58
scripts/ai_agent/config/agent_config.json
Normal file
58
scripts/ai_agent/config/agent_config.json
Normal 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
|
||||
}
|
||||
}
|
||||
24
scripts/ai_agent/config/model_config.json
Normal file
24
scripts/ai_agent/config/model_config.json
Normal 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"
|
||||
}
|
||||
|
||||
}
|
||||
32
scripts/ai_agent/config/rag_config.json
Normal file
32
scripts/ai_agent/config/rag_config.json
Normal 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", " ", ""]
|
||||
}
|
||||
}
|
||||
}
|
||||
70349
scripts/ai_agent/memory/knowledge_base/map/src_data/export.json
Normal file
70349
scripts/ai_agent/memory/knowledge_base/map/src_data/export.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)。"}
|
||||
318
scripts/ai_agent/memory/knowledge_base/map/src_data/first.ndjson
Normal file
318
scripts/ai_agent/memory/knowledge_base/map/src_data/first.ndjson
Normal 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]。"}
|
||||
80985
scripts/ai_agent/memory/knowledge_base/map/src_data/map.osm
Normal file
80985
scripts/ai_agent/memory/knowledge_base/map/src_data/map.osm
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
0
scripts/ai_agent/memory/long_term_memory/.gitkeep
Normal file
0
scripts/ai_agent/memory/long_term_memory/.gitkeep
Normal file
0
scripts/ai_agent/memory/short_term_memory/.gitkeep
Normal file
0
scripts/ai_agent/memory/short_term_memory/.gitkeep
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
47
scripts/ai_agent/models/model_definition.py
Normal file
47
scripts/ai_agent/models/model_definition.py
Normal 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
|
||||
429
scripts/ai_agent/models/models_client.py
Normal file
429
scripts/ai_agent/models/models_client.py
Normal 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()
|
||||
|
||||
|
||||
|
||||
301
scripts/ai_agent/models/models_controller.py
Normal file
301
scripts/ai_agent/models/models_controller.py
Normal 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: vlm,llm
|
||||
# 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")
|
||||
|
||||
150
scripts/ai_agent/models/models_manager.py
Normal file
150
scripts/ai_agent/models/models_manager.py
Normal 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
|
||||
|
||||
|
||||
757
scripts/ai_agent/models/models_server.py
Normal file
757
scripts/ai_agent/models/models_server.py
Normal 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: vlm,llm
|
||||
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: vlm,llm
|
||||
# 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")
|
||||
|
||||
0
scripts/ai_agent/pnc/.gitkeep
Normal file
0
scripts/ai_agent/pnc/.gitkeep
Normal file
BIN
scripts/ai_agent/pnc/__pycache__/pnc.cpython-310.pyc
Normal file
BIN
scripts/ai_agent/pnc/__pycache__/pnc.cpython-310.pyc
Normal file
Binary file not shown.
48
scripts/ai_agent/pnc/pnc.py
Normal file
48
scripts/ai_agent/pnc/pnc.py
Normal 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 初始化完成")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# ros uav agent node param
|
||||
|
||||
uav_agent:
|
||||
agent_config_json_file:
|
||||
487
scripts/ai_agent/ros_uav_agent_node/uav_agent_ros_node.py
Normal file
487
scripts/ai_agent/ros_uav_agent_node/uav_agent_ros_node.py
Normal 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()
|
||||
BIN
scripts/ai_agent/tools/__pycache__/mcp_clients.cpython-310.pyc
Normal file
BIN
scripts/ai_agent/tools/__pycache__/mcp_clients.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
159
scripts/ai_agent/tools/core/common_functions.py
Normal file
159
scripts/ai_agent/tools/core/common_functions.py
Normal 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 {}
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
0
scripts/ai_agent/tools/info_collection/.gitkeep
Normal file
0
scripts/ai_agent/tools/info_collection/.gitkeep
Normal file
124
scripts/ai_agent/tools/mcp_clients.py
Normal file
124
scripts/ai_agent/tools/mcp_clients.py
Normal 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}")
|
||||
|
||||
26
scripts/ai_agent/tools/mcp_clients_config.json
Normal file
26
scripts/ai_agent/tools/mcp_clients_config.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
24
scripts/ai_agent/tools/mcp_servers.py
Normal file
24
scripts/ai_agent/tools/mcp_servers.py
Normal 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
|
||||
0
scripts/ai_agent/tools/memory_mag/rag/__init__.py
Normal file
0
scripts/ai_agent/tools/memory_mag/rag/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
206
scripts/ai_agent/tools/memory_mag/rag/langchain_llamacpp_rag.py
Normal file
206
scripts/ai_agent/tools/memory_mag/rag/langchain_llamacpp_rag.py
Normal 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)
|
||||
|
||||
286
scripts/ai_agent/tools/memory_mag/rag/llama_cpp_embeddings.py
Normal file
286
scripts/ai_agent/tools/memory_mag/rag/llama_cpp_embeddings.py
Normal 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"]
|
||||
@@ -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")
|
||||
194
scripts/ai_agent/tools/memory_mag/rag/map_rag/osm_map/ingest.py
Normal file
194
scripts/ai_agent/tools/memory_mag/rag/map_rag/osm_map/ingest.py
Normal 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()
|
||||
337
scripts/ai_agent/tools/memory_mag/rag/rag.py
Normal file
337
scripts/ai_agent/tools/memory_mag/rag/rag.py
Normal 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()
|
||||
249
scripts/ai_agent/tools/memory_mag/rag/read_vector_data_base.py
Normal file
249
scripts/ai_agent/tools/memory_mag/rag/read_vector_data_base.py
Normal 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
scripts/ai_agent/tools/model_evolution/.gitkeep
Normal file
0
scripts/ai_agent/tools/model_evolution/.gitkeep
Normal file
0
scripts/ai_agent/tools/prompt_mag/.gitkeep
Normal file
0
scripts/ai_agent/tools/prompt_mag/.gitkeep
Normal file
0
scripts/ai_agent/tools/visualization/.gitkeep
Normal file
0
scripts/ai_agent/tools/visualization/.gitkeep
Normal file
Reference in New Issue
Block a user