From 12cfcc26818e02b37f3c6df504fdd4b5ceddaa88 Mon Sep 17 00:00:00 2001 From: huangfu <3045324663@qq.com> Date: Mon, 24 Nov 2025 20:10:33 +0800 Subject: [PATCH] first commit --- CHANGELOG.md | 381 ++++++++++++++++++ scripts/ai_agent/config/model_config.json | 8 +- scripts/ai_agent/config/rag_config.json | 8 +- scripts/ai_agent/groundcontrol | 1 + .../chroma.sqlite3 | Bin 0 -> 163840 bytes .../map/vector_store/osm_map1/chroma.sqlite3 | Bin 8925184 -> 8925184 bytes .../__pycache__/models_client.cpython-310.pyc | Bin 7508 -> 8463 bytes .../__pycache__/models_server.cpython-310.pyc | Bin 16357 -> 16293 bytes scripts/ai_agent/models/model_definition.py | 2 + scripts/ai_agent/models/models_client.py | 68 ++-- scripts/ai_agent/models/models_controller.py | 31 +- scripts/ai_agent/models/models_server.py | 219 ++++++---- .../common_functions.cpython-310.pyc | Bin 1218 -> 3675 bytes .../__pycache__/image_process.cpython-310.pyc | Bin 5301 -> 3039 bytes .../__pycache__/image_process.cpython-313.pyc | Bin 0 -> 5097 bytes .../image_process/image_process.py | 99 +---- .../rag/__pycache__/__init__.cpython-310.pyc | Bin 208 -> 167 bytes .../llama_cpp_embeddings.cpython-310.pyc | Bin 9095 -> 9054 bytes .../rag/__pycache__/rag.cpython-310.pyc | Bin 7861 -> 9934 bytes scripts/ai_agent/tools/memory_mag/rag/rag.py | 98 +++-- 20 files changed, 668 insertions(+), 247 deletions(-) create mode 100644 CHANGELOG.md create mode 160000 scripts/ai_agent/groundcontrol create mode 100644 scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map1/a5905126-f6c3-4117-bbdf-3e7305d6d359/chroma.sqlite3 create mode 100644 scripts/ai_agent/tools/data_process/image_process/__pycache__/image_process.cpython-313.pyc diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7c2f32d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,381 @@ +# 项目修改日志 (Changelog) + +本文档记录了项目的主要功能修改和代码变更。 + +## 修改日期 +2025-11-24 + +--- + +## 一、模型服务器 (Models Server) 修改 + +### 1.1 文件:`scripts/ai_agent/models/models_server.py` + +#### 修改内容: + +**1. 配置解析改进** +- **位置**:`get_model_server()` 函数 +- **修改**: + - 修正配置文件路径,使用 `project_root` 变量 + - 添加配置文件读取错误检查(`read_json_file()` 返回 `None` 的情况) + - 支持 `models_server` 配置为列表或字典两种格式 + - 修复 `global _process_instance` 声明位置(必须在函数开始处) + +**2. 消息构建逻辑修复** +- **位置**:`_build_message()` 方法 +- **修改**: + - 修复纯文本和多模态消息格式不一致的问题 + - 纯文本推理:`system_prompt` 和 `user_prompt` 的 `content` 字段为字符串 + - 多模态推理:`system_prompt` 和 `user_prompt` 的 `content` 字段为列表(包含文本和图像对象) + - 解决了 `TypeError: can only concatenate str (not "list") to str` 错误 + +**3. JSON Schema 支持** +- **位置**:`text_inference()`, `multimodal_inference()`, `model_inference()` 方法 +- **修改**: + - 添加 `response_format` 参数支持,用于约束模型输出格式 + - 当 `request.json_schema` 存在时,构建 `response_format` 对象传递给 `create_chat_completion` + - 格式:`{"type": "json_object", "schema": request.json_schema}` + +**4. 错误处理增强** +- **位置**:`text_inference()` 方法 +- **修改**: + - 添加 `messages` 为 `None` 的检查 + - 显式设置 `image_data=None` 用于纯文本请求 + - 改进错误日志,使用 `exc_info=True` 记录完整堆栈信息 + +**5. GPU 日志增强** +- **位置**:`init_vlm_llamacpp()` 方法 +- **修改**: + - 当 `n_gpu_layers > 0` 时,设置 `verbose=True` 以输出 GPU 使用信息 + - 添加 GPU 层数日志输出 + +**6. 主函数启用** +- **位置**:`main()` 函数 +- **修改**: + - 取消注释 `uvicorn.run()` 调用,使模型服务器可以独立启动 + +--- + +### 1.2 文件:`scripts/ai_agent/models/model_definition.py` + +#### 修改内容: + +**JSON Schema 字段添加** +- **位置**:`TextInferenceRequest` 和 `InferenceRequest` 类 +- **修改**: + - 添加 `json_schema: Optional[Dict[str, Any]] = None` 字段 + - 用于在推理请求中传递 JSON Schema,约束模型输出格式 + +--- + +### 1.3 文件:`scripts/ai_agent/models/models_client.py` + +#### 修改内容: + +**Pydantic 兼容性改进** +- **位置**:`text_inference()` 和 `multimodal_inference()` 方法 +- **修改**: + - 添加 Pydantic v1/v2 兼容的 JSON 序列化逻辑 + - 优先使用 `model_dump_json()` (Pydantic v2) + - 降级使用 `json()` (Pydantic v1) + - 最后使用 `dict()`/`model_dump()` + `json.dumps()` 作为兜底方案 + - 解决了 `Object of type TextInferenceRequest is not JSON serializable` 错误 + +**服务器等待逻辑改进** +- **位置**:`wait_for_server()` 方法 +- **修改**: + - 接受 "loading" 状态作为有效状态 + - 增强连接失败时的错误消息 + +--- + +## 二、后端服务 (Backend Service) 修改 + +### 2.1 文件:`scripts/ai_agent/groundcontrol/backend_service/src/main.py` + +#### 修改内容: + +**子进程输出处理改进** +- **位置**:`start_model_server()` 函数 +- **修改**: + - 使用线程实时输出模型服务器的 stdout 和 stderr + - 移除 `bufsize=1` 参数(二进制模式下不支持行缓冲) + - 解决了 `RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode` 警告 + +--- + +### 2.2 文件:`scripts/ai_agent/groundcontrol/backend_service/src/py_tree_generator.py` + +#### 修改内容: + +**1. RAG 初始化改进** +- **位置**:`__init__()` 方法 +- **修改**: + - 从 `rag_config.json` 读取 RAG 配置 + - 计算 `ai_agent_root` 路径(相对于 `base_dir` 向上三级) + - 使用配置中的 `vectorstore_persist_directory` 和 `collection_name` + - 添加向量数据库加载状态日志 + +**2. JSON Schema 支持** +- **位置**:`generate()` 方法 +- **修改**: + - 分类请求:传递 `classifier_schema` 约束输出格式 + - 生成请求:根据模式(simple/complex)传递对应的 schema(`self.schema` 或 `self.simple_schema`) + - 确保模型输出符合预期的 JSON 结构 + +**3. RAG 检索调用更新** +- **位置**:`_retrieve_context()` 方法 +- **修改**: + - 将 `score_threshold=0.6` 改为 `score_threshold=None` + - 使用自适应阈值,自动适应 L2 距离(ChromaDB 默认使用 L2 距离) + +**4. 正则表达式修复** +- **位置**:`_parse_allowed_nodes_from_prompt()` 函数 +- **修改**: + - 修复正则表达式,正确匹配 "可用节点定义" 部分 + - 解决了 `ERROR - 在系统提示词中未找到'可用节点定义'部分的JSON代码块` 错误 + +--- + +## 三、RAG 系统 (Retrieval Augmented Generation) 修改 + +### 3.1 文件:`scripts/ai_agent/tools/memory_mag/rag/rag.py` + +#### 修改内容: + +**1. 向量数据库加载改进** +- **位置**:`load_vector_database()` 函数 +- **修改**: + - 添加集合存在性检查(使用 ChromaDB 客户端) + - 列出所有可用集合及其文档数量 + - 检查目标集合是否有数据 + - 自动检测并提示有数据的集合 + - 即使集合为空也返回数据库对象(允许后续添加数据) + - 添加详细的日志输出,包括集合信息和文档数量 + +**2. 检索逻辑优化** +- **位置**:`retrieve_relevant_info()` 函数 +- **修改**: + - 将 `score_threshold` 默认值改为 `None`,支持自适应阈值 + - 自适应阈值逻辑: + - 取前 k 个结果中的最高分数 + - 乘以 1.5 作为阈值(确保包含所有前 k 个结果) + - 自动适应 L2 距离(分数范围通常在 10000-20000) + - 修复日志格式化错误(`score_threshold` 可能为 `None`) + - 添加相似度分数范围日志输出 + - 解决了阈值过小(0.6)导致所有结果被过滤的问题 + +**3. 嵌入模型配置容错** +- **位置**:`set_embeddings()` 函数 +- **修改**: + - 使用 `.get()` 方法访问 `model_config` 参数,提供默认值 + - 支持的参数:`n_ctx`, `n_threads`, `n_gpu_layers`, `n_seq_max`, `n_threads_batch`, `flash_attn`, `verbose` + - 解决了 `KeyError: 'n_threads_batch'` 等配置缺失错误 + +--- + +### 3.2 文件:`scripts/ai_agent/config/rag_config.json` + +#### 修改内容: + +**配置更新** +- **修改项**: + 1. `vectorstore_persist_directory`:更新为 `/home/huangfukk/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map1` + 2. `embedding_model_path`:更新为 `/home/huangfukk/models/gguf/Qwen/Qwen3-Embedding-4B/Qwen3-Embedding-4B-Q4_K_M.gguf` + 3. `collection_name`:从 `"osm_map_docs"` 改为 `"drone_docs"`(使用有数据的集合) + 4. `model_config_llamacpp`:添加 `n_threads_batch: 4` 字段 + +--- + +### 3.3 文件:`scripts/ai_agent/config/model_config.json` + +#### 修改内容: + +**模型配置更新** +- **修改项**: + 1. `verbose`:从 `0` 改为 `1`(启用详细日志) + 2. `n_gpu_layers`:设置为 `40`(启用 GPU 加速) + +--- + +## 四、主要问题修复总结 + +### 4.1 已修复的错误 + +1. **模型服务器启动超时** + - **原因**:`main()` 函数被注释,服务器无法启动 + - **修复**:取消注释 `uvicorn.run()` 调用 + +2. **配置解析 KeyError** + - **原因**:`models_server` 配置格式不一致(列表 vs 字典) + - **修复**:添加格式判断,支持两种格式 + +3. **消息格式类型错误** + - **原因**:纯文本和多模态消息格式混用 + - **修复**:区分纯文本(字符串)和多模态(列表)格式 + +4. **JSON 序列化错误** + - **原因**:Pydantic 模型直接序列化失败 + - **修复**:添加 Pydantic v1/v2 兼容的序列化逻辑 + +5. **RAG 检索返回空结果** + - **原因**: + - 集合名称不匹配(`osm_map_docs` 为空,`drone_docs` 有数据) + - 相似度阈值过小(0.6),而 L2 距离分数通常在 10000+ 范围 + - **修复**: + - 更新集合名称为 `drone_docs` + - 实现自适应阈值机制 + +6. **配置缺失 KeyError** + - **原因**:`rag_config.json` 缺少 `n_threads_batch` 字段 + - **修复**:添加字段,并在代码中使用 `.get()` 提供默认值 + +--- + +## 五、新增功能 + +### 5.1 JSON Schema 约束输出 + +- **功能描述**:支持在推理请求中传递 JSON Schema,约束模型输出格式 +- **应用场景**: + - 分类任务:确保输出符合 `{"mode": "simple"|"complex"}` 格式 + - 生成任务:确保输出符合 Pytree JSON 结构 +- **实现位置**: + - `model_definition.py`:添加 `json_schema` 字段 + - `models_server.py`:构建 `response_format` 参数 + - `py_tree_generator.py`:传递动态生成的 schema + +### 5.2 自适应相似度阈值 + +- **功能描述**:根据检索结果自动调整相似度阈值 +- **优势**: + - 自动适应不同的距离度量(L2、余弦距离等) + - 无需手动调整阈值参数 + - 确保返回前 k 个最相关的结果 +- **实现位置**:`rag.py` 的 `retrieve_relevant_info()` 函数 + +### 5.3 向量数据库诊断功能 + +- **功能描述**:加载向量数据库时自动检查集合状态 +- **功能**: + - 列出所有可用集合 + - 显示每个集合的文档数量 + - 提示有数据的集合 + - 诊断集合不存在或为空的情况 +- **实现位置**:`rag.py` 的 `load_vector_database()` 函数 + +--- + +## 六、配置变更 + +### 6.1 必须更新的配置 + +1. **`rag_config.json`** + - 更新 `vectorstore_persist_directory` 路径 + - 更新 `embedding_model_path` 路径 + - 更新 `collection_name` 为有数据的集合 + - 确保 `model_config_llamacpp` 包含所有必需字段 + +2. **`model_config.json`** + - 根据需求调整 `n_gpu_layers`(GPU 加速层数) + - 根据需求调整 `verbose`(日志详细程度) + +--- + +## 七、测试建议 + +### 7.1 功能测试 + +1. **模型服务器启动测试** + ```bash + cd scripts/ai_agent/models + python models_server.py + ``` + +2. **RAG 检索测试** + ```python + from tools.memory_mag.rag.rag import set_embeddings, load_vector_database, retrieve_relevant_info + # 测试检索功能 + ``` + +3. **JSON Schema 约束测试** + - 发送分类请求,验证输出格式 + - 发送生成请求,验证 Pytree JSON 结构 + +### 7.2 集成测试 + +1. **端到端测试** + - 启动后端服务 + - 发送 "起飞,飞到匡亚明学院" 请求 + - 验证 RAG 检索是否返回地点信息 + - 验证生成的 Pytree 是否包含正确的地点坐标 + +--- + +## 八、注意事项 + +1. **向量数据库集合** + - 确保使用有数据的集合(当前为 `drone_docs`) + - 如果更换集合,需要更新 `rag_config.json` 中的 `collection_name` + +2. **嵌入模型路径** + - 确保嵌入模型路径正确 + - 嵌入模型必须与向量数据库创建时使用的模型一致 + +3. **GPU 配置** + - 根据硬件配置调整 `n_gpu_layers` + - 如果使用 CPU,设置 `n_gpu_layers=0` + +4. **日志级别** + - 生产环境建议设置 `verbose=0` + - 调试时可以使用 `verbose=1` 查看详细信息 + +--- + +## 九、后续优化建议 + +1. **性能优化** + - 考虑缓存嵌入模型实例 + - 优化向量数据库查询性能 + +2. **错误处理** + - 添加更细粒度的错误分类 + - 提供更友好的错误消息 + +3. **配置管理** + - 考虑使用环境变量覆盖配置 + - 添加配置验证逻辑 + +4. **文档完善** + - 添加 API 文档 + - 添加部署指南 + +--- + +## 十、启动脚本 + +### 10.1 后端服务启动脚本 + +**文件**:`scripts/ai_agent/groundcontrol/backend_service/start_backend.sh` + +**功能**: +- 提供便捷的后端服务启动方式 +- 自动设置工作目录和 Python 路径 +- 启动 FastAPI 后端服务,监听 `0.0.0.0:8001` + +**使用方法**: +```bash +cd scripts/ai_agent/groundcontrol/backend_service +./start_backend.sh +``` + +**脚本内容**: +- 自动切换到脚本所在目录 +- 设置 Python 路径 +- 使用 uvicorn 启动 FastAPI 应用:`python -m uvicorn src.main:app --host 0.0.0.0 --port 8001` + +--- + +**文档版本**:1.1 +**最后更新**:2025-11-24 + diff --git a/scripts/ai_agent/config/model_config.json b/scripts/ai_agent/config/model_config.json index 8a2b87a..5e8bbe6 100644 --- a/scripts/ai_agent/config/model_config.json +++ b/scripts/ai_agent/config/model_config.json @@ -2,16 +2,16 @@ "models_server": { "model_inference_framework_type":"llama_cpp", - "multi_modal":1, + "multi_modal":0, "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", + "vlm_model_path":"/home/huangfukk/models/gguf/Qwen/Qwen3-4B/Qwen3-4B-Q5_K_M.gguf", + "mmproj_model_path":"", "n_ctx":30720, "n_threads":4, "n_gpu_layers":40, "n_batch":24, "n_ubatch":24, - "verbose":0, + "verbose":1, "model_server_host":"localhost", "model_server_port":8000, "model_controller_workers":1 diff --git a/scripts/ai_agent/config/rag_config.json b/scripts/ai_agent/config/rag_config.json index 68a05b4..ebbb150 100644 --- a/scripts/ai_agent/config/rag_config.json +++ b/scripts/ai_agent/config/rag_config.json @@ -1,16 +1,16 @@ { "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_model_path":"/home/huangfukk/models/gguf/Qwen/Qwen3-Embedding-4B/Qwen3-Embedding-4B-Q4_K_M.gguf", + "vectorstore_persist_directory": "/home/huangfukk/AI_Agent/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map1", "embedding_framework_type":"llamacpp_embedding", - "collection_name":"osm_map_docs", + "collection_name":"drone_docs", "model_config_llamacpp": { "n_ctx":512, "n_threads":4, "n_gpu_layers":36, "n_seq_max":256, - + "n_threads_batch":4, "flash_attn":1, "verbose":0 }, diff --git a/scripts/ai_agent/groundcontrol b/scripts/ai_agent/groundcontrol new file mode 160000 index 0000000..d026107 --- /dev/null +++ b/scripts/ai_agent/groundcontrol @@ -0,0 +1 @@ +Subproject commit d026107bc214ea16a425bbb2cac100e8497aadf2 diff --git a/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map1/a5905126-f6c3-4117-bbdf-3e7305d6d359/chroma.sqlite3 b/scripts/ai_agent/memory/knowledge_base/map/vector_store/osm_map1/a5905126-f6c3-4117-bbdf-3e7305d6d359/chroma.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..f150ee04ea71a244160e50dffd07ca65706c9a78 GIT binary patch literal 163840 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 delta 721 zcmW;Gxl8Gml_o>5QD@hfl`NY zGGj$yK?NfzX#EG6v4b*AYG@n^8oyY4n)l|-yhw7%7QxpX)$=X4QT6;9{8^!A%Yq_% z->tOr_|WfYQNo=pmBfNS>~`s^yNHHRiKMYbd+tE5w6E77{u__kLqjIa3B9_oN18}dT})a?=vbg0WNU7X7Pf?c&Z zrJrio`oCOsg$n0=K^1AxV8a3EIEZxE;eZncOjr=Okbz8O;Sdhv2#z8f$8a1u$VDDb zARlg=ga-xK|8ojOC`JiRqZDW0MH$Lbfl5^2EPSZOIh;ogF5n_Ap%!(h$7T3&1r4~0 zMqI;n+`vsVp&2b`#Vxd<9UbVzZQMZ@?xGt#xQF}bMIRm@fPM@hh(SEW5FX(%hB1Os zjA0y4@D$Gw!gIX9OHAMuUgHhk;vFV2h4+}o2ZS+$kC;USpAf}oe8C)I>%QRp#-D#p CB=M*K diff --git a/scripts/ai_agent/models/__pycache__/models_client.cpython-310.pyc b/scripts/ai_agent/models/__pycache__/models_client.cpython-310.pyc index 026201d6b302590749e1586e1b4f03925b5e67d3..a14016647010ca94e478bb7425362cb48ceba089 100644 GIT binary patch delta 2996 zcma)8O>7&-6`t8${*x@dQT^>SlWA8m~3V~ufrym3T7 z(wNXEDBtj7EyGiw9EXR5`i9XFW zmS2^`Px++ zBOt#MD$oK2<$VH2_l=)daW5NVirgVP{odrL^Gv zS$U^afDCv<#4vRKzA(GoZ1CCTwrMZb+N-PH;lU~I7m)(Jy7QaJy!>hr7Kt*#D1e@H zTvNCPhR=^K2a)wD4@8Aff>nOcUDI-nT2mMf7k9W=^rjPk>x?5S*5Hm~F7X8|=Ep^t zWR93Z1w+Ws3e$3UxYsq^w&N=+PSX}gam6tJO%`}2+#F_;NhgsFoq3m{2p z8mHdF?dq&bz|8OMtbLE?K9HK9EFeoo>->vLV^UfF=R005mC} zWnF1Y4(Kw_%RpDQi7i7P*(Td`Lvo`lv30T%X9|m0^!mEQqMyhc^18g0*oJ!vJqTJP zw}?XAh|Y$%khrCH2Ptt$_xMHfkw+$Jo(q1;#C4hQ{RdY5R4FL$<6i?rnT!0qI9 zgS8tiBkAFI^3FNN6=3 zSM=lJd4#hj7*u3T>zuOOU8ID-&c8xKqn7|_rH zG*?_eIB1KO3~jlJtWd|RI6SDtkG0Hu^`^<5ZE24S<+E$(+?My3%q$)8(rRgR?wQ#p zz6;8<#LmY(f1sVcxC8;|8um!`<+iAcvv{z;-@73d=aJIg7TAZu6c-UNilGYzaCFdc zaS3VruZ;pp(C6jyeWl+4D~wY1=_vi9cYdd$8Z;(up@ie!&D>F{d-rp1c5VQ{Pg)JQ z-v%?GQane{-hw?4guUeQqh_ncZ6;DE{RtBeM)8rlFLsNBTqWX%xapZ%e}r62Q(tMB z?y`6j=Rxl7_XDsC=mu%-@7}L_-=^c9oFAie-Wj#Hb1r{@c0#*x?`?nuq2ur!gm(e7 zm8?Y>dng&b;YaJur6ult)fd0ib<7~L4?0E|NPEJj z-7>Lv6;Dv_^}^kYeXvxM#39^q7{?J2T!7b*a2eqW0F>*6g>cc6jDn~1Pjs*}ixC73 zz*jzKHtS**=Q0B1772>7NaN`H0DhzfH_8=Qd<9lxaT6hf@B;)BfgyA)2}bzFh&%wE z%=sBWJVml}FwYQU#>lU|N zQP^1#eH?peMZ%ExSp*x?V7a|)Lo-y)wx*l zepQ^js;6A|dfKgBI@AW3g!cjyPls11_&VBW85-s1!WtYN&A?svy!^068{V1W`8QVa z3{=#^t^wV?{d=DAm4<2AKc?Us^r}jQ4V@o145S+dNWiyFoAG`-e1=YV{~aFg=<ztne delta 2005 zcmbtVOKclO7~Wa0-}c(E9mjTJ$BFaWrZ1ZC3TdULNt%e@q%CbFgIFz|apKh8b!OLv zfP{q_aX=ti4Tl~<98e`r_*@Bb<0Wz6G#5@#^~4qRK$zbyb%+`+*qU!<{`X^M_LGHQ z=2R;j4oKqn`=<}K#!lQ(hfwOy^beOwXge>)2oB)j$1;*g=(AB$_#BnSE=o9r!+R1A z-;zt?IF3~u*+ZoX9L0TmQfZPDaO{RmKO~d4BqU*Ur8Bey!>Gm9s-dl|6S!!K3UO`FP8(`Xm0Rzt11T1so?XoFH!8Pl7lp#(>zTNQi`qD=noZ z>?c0rBL0e;mMSVv;V`cDw*3g0VVe74<%*SvR=Y;vl|1y8H^|CLG--{=m>1Z zupv2F4j~_6Bf$ywk@^rd+wstKG^KfXuqo8_2DR!9hdu`?&jY*w@FKvR0QN!nOlw*! zikgp}5HnXAmhI3!5I6;UH*VG&Ha!W1UYvnWob*|s9%Y+*8^&(UGB7<2Vo%A0s}6bF z3F=j|LMSoIpH^aV>Nud>;Afz{%f_E*XCsC5xUpciySK#{J(@@0TE0h+g4 z?T(B84B#FAG2j^4w?cO z6Tp=>EvrV4!QKU+0Km22?us~Z4s`=T23Q2R1n??AFZymXfTjw7lG}F$_@pRGg_A>a zP)6Oq@#qy98lrD>r!oZ56MvFESL#O9ydXXKgUK%592QW0%Wj zqkF^PAc0B9P}d=R+p66poc%nUD)5karM5}g(WzxQv>;^Z20-VCLhCsT=4aVXet7;W z&@Tf(*Jvjxi1(?P`Q}Aynl*AxBq!PP?8p3MN^vQoub#gGimFJh0Pzj@+ySKQ{|(5x Bw3Gk< diff --git a/scripts/ai_agent/models/__pycache__/models_server.cpython-310.pyc b/scripts/ai_agent/models/__pycache__/models_server.cpython-310.pyc index 69b78442d926dcc0993c98340d691f9f7f60c95f..bcff3596d33544b9ffeeac635ddbeeadd86fd6cf 100644 GIT binary patch delta 8114 zcmb_hdyrIBdcWuP?c29+_w@9ivTPbH?D^;2*Yf~F{3-f*F z_Ph|NO;u*<{_eTweCPdrzw>>khf6P&LYb;6U4p;Y7Ih>qJ#sSCR=Qu7Bq~vok_`7v z3qt+Dq!tx~Yq_ypr0Y{Ftd+(}Yn8FeT5YVh));F1F~_K*)<>;+iAqSt5>Ge>5QX_ zvBR8eZXX~ZHUW8N28_8l`iN5@WZynaH>G<`W~LM7cJsl!nX{eNJM$?!X=P|U^|i9_*K%qmReHUZw@U+eqSiyhWLkTl$Co^fsn@gEymfbSeqT4LL4E0V7HP@5~ark#?r!_h?mIWi~ zi@}Tbl8m=%tiZASPeKvZ#uE;~Xs9ewnteMeZeQ@uEX5xMG4Mb7jxH9VoSlZTiPKh zlCAfDLZo7VNQatiwJ$&$U;~0M??$_-5EvjZ4}~_-W@O;q(l>$Hih+j%F3Dl)AUP;S zTX?Z`Mv1{LRt#e{rqHGw)m0itv4P;CGQ9h6Bw4iB6de*DdmcOD-(@+f8*JG_2eCw3<((sGtF1__qPh{s=;%MeW z381##Of}qx&su*ZR=D(%=V3T`b44OwJGEqo92@=&4Br-hOW!a|?^|G!g9!1t&e?ABx$T{boAn1b~4+f#PQMeuB3&tQjF+tLO!w^62kDx$-|yC8Ceyv5H&?j-{}G<`}* z0bZ!0{^MFvE=c820kE$cSk+Sib_FKhWlLp+3bwGlG(dw?J1##$AeYdpf@hTa>~J|e zC>0f|2tKZ2jD|$Z)xbDjAsrSJDfM~!Y6{5d8ZuG|K!+ztYYJXkd)!x4-KGjkGjMW2 zrFAVpWbib=)7T=FYoQyhpcQ;}9Sqe|t}kfihCyi%jt2bhYb-a>ra?lRTO{~yrY)xx z+Im{9@SL~*T>|bIksd+TYw2J2Z$Nu~L3`OxtKX(w&&~#C$P-n5X|? zzo5OT{14N+N)O{6pvAJo09)7*Og@4zfN&HbsxefaD>X$NmZ&Z)3*ZF$a+!22m)K`o z@jNxq`skVAQ!hleWYQ*k7AobDPAfIP{mR9szcsOb z9<|vmy!iZ4x1=Xh={d8f&rH~7U19`t5(D||EU5A62OnL0=9S3i9b30Vc8KiA#rIzx zIrH61AM(=hWAD3sy#B0@9RTSorzvLm6cjksV0ywajrC@j6}Q=O6c@*aA5%DV_HBd{ z2u~w$gy#TuvNT|H$~d_SpJ&|!A4u5h46Jbd(2N9k}IE8?g z%T6O)VYm7Oy9IY|)NYyUxVy`62J3wf;RS^6B3#p8L-oR9$C^sQs^t3k&t|r7Lk3oV z7t%G44m=!|HKHId%PtG6z^zKVOQ+73mAGWx~A(pnFj2Zc#jfgGAW z`afatoPaQdMSIKML2&IV799jju7W27Pc>L{&8S5e6k8KEU0Xr(gV$3rXr;e6!Jr%adBvUswnFP^gWWV{&tBv$nAWJ>Hu7z=wwETGC=)vI=eS3I zf$$Q*diF9VudAeI6?PJYoceU$svNpVyBIT>l>G_T7iWo3W3rWNatc>-hNw!a0O1!j@_`#zc!;A)@jxuQD;uA2qRdeq?qhAMwsA z`C>7eNyK8T3wuP8XSX7F0UU)U6Sj!gn1Y#iB8V_AhAE05*iC3b1V76N-$$5^@I!l<7v82XG(X6rI(zF*G-{p>eawVS6AOU9hn~9GnWz zI(Ty5BK%Zz8ac?%M_Wrp0zBaXXW>RT=rstlAnu{QN=O6Y2=!ym8JX(B1Oo+ffUvbf zHi!&jqfC`s3M~a%AoK(7k?jC*&sGQtfhp3Y;5sGP&jX@vb){}eqd+#oD*+lBAP^lY zeN{QTpnyLk!Zt`%gFgf80pD<@7nHb?UTI?-iIQ*GUKp@)MlMP=M4r$>WkR{*)E(@1 zM2EFwgRPr5*zFTW`_iP*UULA@9D$GwI){!M;2p*Y3!5(}J5b&C&pD`qC*Pag%P-Bj zp_Io>g7~6x;{j2`0h9GaMxHB=4E^b)H_i{Ac=k7s9>4g(>m$!T_UZX|BAs87PS+1; zg^Je_11B5(5~oV&crNBPo5#~|a$j-mY+?tWzW0NR7v3L!^JJuRQr*tT@DH9FdHroM z-v3Jl-adCx$#q&zA0Iw8SgCvZ`@@f)8UEgr!%scS?uB9H=X`B!*INg-!D}qi*VdFj zId@^HW&-v0Kxb?@!cqVyh<8rl8^u^AW4pI1Q?O86ydXJ4Oqffi!E1-hJ4Z<+!TZO3 zb1!9t84C+WCju_}!tVaYgumCDOhI^<$dqu)-Jr5|(#ncFH}VX+l;rN#s7*sRXw6P| z^3Iv;W*N-&4k&8$K$u2!qQPZjE&RKVgysQv0P-QE19w7?f&h3DU#Sq);T@F*-h(Iv zuK_D#S*u4O>?i!>yf4U)`J-LAWk1GAu0~jcFt%(EU9ZIqQ9hYh1>Ze5v}k@4St1S| zQC+O0h=?k;Nq>vg4TQV88O=TGpF&BRFIdu0 zrH4tq7alc4*iZRgOG+fn|7l76-M<0}xFz?%-PC9k(1PF$y>goYMW4WO=kSyd%6d?d z*(Z=pz%%xTgVx|#2M3iD6^RY;ysf}t9f9N9D-LS{hoZL+?_6c-gL7A=8X_O) zo*j4}>az0)AMkg&XR5-=f6o8Cdz!Wea-%A9xVp5fwHZ*dj@yWx@K{t$p#$2Rczvda&Q-VEZ;5>8dxG-)iC zJY>dFmYtLyfTOin{Ee)xBtBaWwwuTL=*M5_w8t^;NdVAKVO=f!-JbRButuWF<5>Iz z!Z8F_nt#M|(0G_Qex_k+Is#hm1m2F|r%=E$q@uw|_e^+SE1LWfN{K}l z_9J?H4Dy_68-A>EgCF=wrWz&>SEPGmd*V5>`zD9Kxx9V86BOa?XnZWxSAbB%klVm$ z82uIh{c>y7XV^6Ss)Rcji@_og9NRbGr-~t7j5BjS=x>fS zU};UIG?urMsoea;o&&6r|L4k%T8t9m7O@|Ca<4`HgHK%^f89@=D7&9<66QP5@y=sO_b*tgq z@!c6u!wocJ?_udi1oVyA%?LuhR!{A2gF6tsamIV-Ah>8(!_R~*2zqh$u#M7Cas%uJJZgj*0|}k&A5MQ zV!7|_fdnW~?Ub1N-P_%_dvD*q_xA1bOQnA;g>rRuehL2m{=;9K`|nFPLhYs3WJ#hD zC21)`3__5W({e^J6hSL#)li{MNqY>Bcx#3RZ#C`B=!Ty08NQ6)@Mi)>AQLo#l|FSw zo!|`_A@Qy^>f!B4hcgXELxrc&XoR{Ow|n*|>7~X}!PjoI!`qi$mRW8r7kvKoicE*m zQR%bNSOx8YbZ2I@u{zUbbY<2Uyou~6h0_}|n~Y5rwmzfJY%n)ZlXCxzWDJ-C6I*CQ zR5G@j+i0V?by_Bp>6zGG-eK;95}AQ%LrW9ZVHK*zzi`CnswzzXQc8W`1E1gY96N1T@r1B_fmLE z@NS1U)-Qv%lK41nO&mEW(dBf-Rf(=JmrpCkQ7CuNl~7(O%EM4zMLVI~DaxOK@@m=z zTU(y*WBffNwqR z1-{;Cd72m}foC=FY@i!~=P|m8_Fa{XQ*uCT}G626Ae8H{M%4MS4nzt# zkrU=K_Mz;U$;@oh95FBOLDlnQpkSFSo@cpC-hThbAcy16B%X=ex$|b$x-%yJ-rMU0 zY%`NLS;8(bvr_vXdmWFR%g6J>PMtMr0YhaNN420n7UBb4T_cKRGp9ZtPh_(>J7K4C zSt}lA-S{CTm9^P=q&#EkT*77>fMy#Jya<~RRDjzO+m1BC-3k7}wm9GI4K7;;#i!u0 zLI8eACqeNGlUBmk@#Ec*s)8qg<3RSPuf~g!LkzMuuILpf+M5)`!)b5&97Z5VjzoqTDgIB86idW!sQkOs(_!bTzvJ z`E~+Oc&so$Ri8m3G`e%4Mj7xtsL_hadgsm0HC#+a+1I4U$GX4bA0=sNt$^KijK!h zn`WLpj@&iFz7&X(@65IZN(6>`CAgx+XvkzhMH6udEoM5NPuS;T(DXOK_2eo3VQ^?Q zYKoZ%V+i92=MYi|7zo*Uo~#=#oka2s0#1lw5M_@6I3AO+9Ag2jIE8Q)0V7S!!=6Ce zKoH*;0m>m}t;kr@AoOrCor$N@iA*Ay&;JCfEeu(@su8~|%d;CpvqZ~54Li->ub*lX zib5N3<=GDuFNG7XoG6$pj97;(7V+CGjUal(kvfAQ=zOX zrT!;0&x@+_c*9ALh#dTf4X+H~+QbxhM2vxMOsQEhsB`M#XA7w`t?Y*_gum1{$Uoi@ zELmTJNMDpGDZn0AlqRGp`SLf%r75LICJ2jC8QQdy=!T1P|Jy{JbeY)uhbe^c;W*gww|1`Z!>sML2Cl~dBF zBm~;CK%hSXzLLVeyilSC1Zu4cGy|f}f2MJvD72~a!E5sz3k9)UK~%~1PyU=pQ$8YH zj`V{PBB1ubs}gOtgGJvofqALZFzZv&6OjE+`JVMr0$H4(E|E)8tcBl-tQvMSH^qs` zj>>XrlSOfzRZN~-&k^2$QS)*zZD!+QbGLSMFj#NtvOhs-hp6fh9r>>2?yZh$PeMv{ zSqx-DZWfan1}8R!H6Fn}SM@B`p9Uzj^V`kKN;^7Ybxb%grb~{N$mh*06?qVOmj3h`*qU>%$wYd33qmWI=X~R@j`T4vxqjRw%alrM5afu{U_0-rD|*v zai|)W8jcS|}eQbw_qdUYwCNNO(AkHhxLY>xzIAp&5BfvFpqAGt!g_L4!VkFZs zB*261K@1}>hUWGvs;$zL7koApm1sRYVR+z7T5f<5^`c(%+Ko6;xv8j^BQw&D88S`8 z(9Pu*+BidKQxvxFi6u00Q>D!}WsEl9i%^RXA-mrb{#<&4zNCl1(9qnv>NsjHgE)V zf2JTrONhUV@HvE+5&i(-6@*s-Vr@0Px|l6tPWgWb_&lxWB zzAFf~5dILM3*qw!B0N;L{hAnIz}8+lLdKbSL7ZoV9?ID72Eq)&YY2k~b0J}-`o?dT{8Bk~LT44_@YQwL9o!j1{{>9IX!&kXF% zV4qqM_DZlzA3T0|a63ec_ZGo&i>j>)3k?^&XtnbO+E{L)K`_ud=n(?rgsrk-oa*E_ z*(wqEJ%_d$nVbS!)nP*l*eVUv2D`bai=9ZOo@?@RgvwL;v%1)Q8u{}pN6suX(gW;E zFuMor3+v|1>$b{%4=RjEwvef8MwpFYW^GdT`^YQelxu+e$f}M~%c90tjbT;Gx`N%s zG3m=lE#?8jycV;pd84}Rt41qU#}(>d{}r;@1(r zfv|lZgag#O`TM*F$npZo+l1)KLqy+Ky6+t1c;j)JOTx{{qxhM_2u~t-0UVX4lD2zQ zpkXCN5GnKpqzVWkw1_2Mhs7@-d<6k-1=u$c{s;h4+jugaSY=sBGo6O-Ri`zKJUBOY z5MY}Bs;g`L!BBUozhzL=Fbk@a@XY@yK%qeBtkz#keD|6aWIsQ>rmcjDEhe&4qzK6{ zB+DCZ39?ij^HbYLebi3_=qWaAF_7HKMR_7%?JLScciwABjN zsfTmHH^6;itRX_n$lez3zYCJw+<+Aay6eOs)2RK33%3Seat>8H4u9&dF@VIGL&XF$1 z02*O0bZn7zOuir#;~TOoNYJnhZuF6SMa3grrPQXVgnbWA&mWNQ^8G#5_V1#Q)s$#{ zl90+iflZ>lYBj=O&{Q9r{dLb0G9coE=q~0x04;OJt+%lI+-_1kk7i!#Z6*_ZwD(E! zCV##6#s6)T?>~H$@`h%u04iXA&Og23n0gU7`SQLfKeMT2q!|i#-hBPNmp}F1&2PN- z*{|NY{`{S55b~9v%0rt*g6wT5#Yh2@ z3w~o|cmKjch2Q-E+U_3>8=gl|e~2A_x#^Q@dx0|?#gkUuk;oy%>>KY%KoXdpmF_npvxA-zPISQCnjJTRu zxX@0cYme?Hz47EZGkKo#f7?7*>cc@dBlIIg5Wa^Xj9$dDKf~f*0L+;+%N1;sIch!! zDYFM#(OmFmOJX95q0Vapn22#gIe&!>KSHPo2p_FF273cqMnO+jD-gQWqUaGl5DA1y z#8Y{+5Mgidf9fxl8i2(KrLxI%ftujEC=&ZIRK|#-z`1Ph3-AHqbpA?422F3_Cs5j; z><_9k`x%f)c;^409X?+PkdFgl!^zafGb{X)0wN(${M~`JHtNApX)6wsdO3k-pETwye-kL2XQ9KjmNCvi>Zb7m?{Z2pD=CZ{cDp znPXW;VTCLh`k!LUNrd~>Rm~()*`Lj6ETp)xLxmI$4tDU9;pe~0-kIIEbq^s+`Si96 z?cc_6Fg(J*)w>zAHuf|RZ@>4rQMn?=J1(OxYnn7}!BHrl&e$pG46yYeZ!eDEb+cmJ z`R`9h=5u#2N#U1mJiFt7%)ZF~c1LGPL|9>B6#Jq_FcDWfklKkLeDE=(P+2vG^#v%# zf(ESpTtX}k^kT@Wri5PzA#MP_QE$V40NjiWF7FJO8O(A#eir^aw58u@tVwDr*|np? z-LAOhHG(2(p3A!LTLQ?aao{;w{)PW^XQt%9C-GVmmnR;-9JrKY=R3yWs>^~~L*e5w z!wDt}%rdRsd?IV68QL6N0YZ{`}|85TQ(O-k}&AH(dN&mQ`>yRL_537FWbIUv)nlDtYafB`abd=v5IAD~kBhrzq|Yz!~M;&Ox) z2p#;^o>gu*L!)uLM-y4NEn)1Bu~mf1V@MrG5K&54nTSn$v2F-Km?TCKHwa=t65)Zp zgA^{SYmHSS5R`~8`mW6894(~HJ&+Ac))fH!&zO7H-HvZ@y#iM(En&P|!IB&#{P^DG zdsI~gSH-)SpfYzouEG`{R>FGN7Y>BgkXI_4W4ZZc diff --git a/scripts/ai_agent/models/model_definition.py b/scripts/ai_agent/models/model_definition.py index 9ffa404..208159f 100644 --- a/scripts/ai_agent/models/model_definition.py +++ b/scripts/ai_agent/models/model_definition.py @@ -14,6 +14,7 @@ class TextInferenceRequest(BaseModel): top_p: float = 0.95 system_prompt: Optional[str] = None stop: Optional[List[str]] = None + json_schema: Optional[Dict[str, Any]] = None # JSON Schema 用于约束输出格式 class MultimodalInferenceRequest(BaseModel): user_prompt: str @@ -32,6 +33,7 @@ class InferenceRequest(BaseModel): top_p: float = 0.95 system_prompt: Optional[str] = None stop: Optional[List[str]] = None + json_schema: Optional[Dict[str, Any]] = None # JSON Schema 用于约束输出格式 # 响应模型定义 diff --git a/scripts/ai_agent/models/models_client.py b/scripts/ai_agent/models/models_client.py index 1a6fdd1..acc7e89 100644 --- a/scripts/ai_agent/models/models_client.py +++ b/scripts/ai_agent/models/models_client.py @@ -135,20 +135,22 @@ class Models_Client: 返回: 包含推理结果的字典 """ - # payload = { - # "user_prompt": prompt, - # "max_tokens": max_tokens, - # "temperature": temperature, - # "top_p": top_p, - # "system_prompt":system_prompt, - # "stop": stop - # } - try: + # 将 Pydantic 模型转换为 JSON 字符串 + if hasattr(request, 'model_dump_json'): + # Pydantic v2 + data = request.model_dump_json() + elif hasattr(request, 'json'): + # Pydantic v1 + data = request.json() + else: + # 降级方案:转换为字典再序列化 + data = json.dumps(request.dict() if hasattr(request, 'dict') else request.model_dump()) + response = requests.post( self.text_endpoint, headers={"Content-Type": "application/json"}, - data=json.dumps(request) + data=data ) response.raise_for_status() return response.json() @@ -211,44 +213,22 @@ class Models_Client: 返回: 包含推理结果的字典 """ - # # 处理图像 - # 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: + # 将 Pydantic 模型转换为 JSON 字符串 + if hasattr(request, 'model_dump_json'): + # Pydantic v2 + data = request.model_dump_json() + elif hasattr(request, 'json'): + # Pydantic v1 + data = request.json() + else: + # 降级方案:转换为字典再序列化 + data = json.dumps(request.dict() if hasattr(request, 'dict') else request.model_dump()) + response = requests.post( url=self.multimodal_endpoint, headers={"Content-Type": "application/json"}, - data=json.dumps(request) + data=data ) response.raise_for_status() return response.json() diff --git a/scripts/ai_agent/models/models_controller.py b/scripts/ai_agent/models/models_controller.py index 40ca56e..4b7ec1b 100644 --- a/scripts/ai_agent/models/models_controller.py +++ b/scripts/ai_agent/models/models_controller.py @@ -102,24 +102,45 @@ 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"] + config_json_file = f"{project_root}/config/model_config.json" # ✅ 修正路径 + print("config_json_file: ", config_json_file) + + # 读取配置文件 + config_data = read_json_file(config_json_file) + if config_data is None: + logger.error(f"get_model_server: 无法读取配置文件 '{config_json_file}'") + raise HTTPException(status_code=500, detail=f"无法读取配置文件: {config_json_file}") + + # 检查 models_server 配置是否存在 + if "models_server" not in config_data: + logger.error("get_model_server: 配置文件中缺少 'models_server' 字段") + raise HTTPException(status_code=500, detail="配置文件中缺少 'models_server' 字段") + + # 如果 models_server 是列表,取第一个;如果是字典,直接使用 + models_server_data = config_data["models_server"] + model_server_config = models_server_data[0] if isinstance(models_server_data, list) else models_server_data 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_config + config_json_file = f"{project_root}/config/model_config.json" + config_data = read_json_file(config_json_file) + if config_data is None or "models_server" not in config_data: + logger.error("get_model_server: 无法读取配置文件") + raise HTTPException(status_code=500, detail="无法读取配置文件") + models_server_data = config_data["models_server"] + model_server_config = models_server_data[0] if isinstance(models_server_data, list) else models_server_data model_server._wait_for_model_load(model_server_config) model_server.init_model(model_server_config["model_inference_framework_type"], model_server_config) diff --git a/scripts/ai_agent/models/models_server.py b/scripts/ai_agent/models/models_server.py index f964744..bf1de36 100644 --- a/scripts/ai_agent/models/models_server.py +++ b/scripts/ai_agent/models/models_server.py @@ -250,68 +250,69 @@ class Models_Server: config = self.model_server_config try: messages = [] + has_images = request.image_data and len(request.image_data) > 0 + chat_handler = config.get("chat_handler", "") + # 构建 system message if request.system_prompt: - system_message={ + if has_images and "Qwen25VLChatHandler" == chat_handler: + # 多模态情况下,system message 的 content 也应该是列表 + system_message = { "role": "system", - "content": [request.system_prompt] - } + "content": [{"type": "text", "text": request.system_prompt}] + } + else: + # 纯文本情况下,system message 的 content 应该是字符串 + 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} - ) - + # 构建 user message + if has_images: + # 多模态消息:content 是列表 + user_content = [] + + if "Qwen25VLChatHandler" == chat_handler: + # Qwen25VL 格式 + len_images = len(request.image_data) + logger.info(f"添加 {len_images} 张图像到消息 (Qwen25VL格式)") + for i in range(0, len_images): + logger.info(f"add image {i}") + user_content.append({ + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{request.image_data[i]}"} + }) + else: + # 其他多模态格式 + len_images = len(request.image_data) + logger.info(f"添加 {len_images} 张图像到消息") + for i in range(0, len_images): + logger.info(f"add image {i}") + user_content.append({ + "type": "image", + "image": f"data:image/jpeg;base64,{request.image_data[i]}" + }) + + # 添加文本内容 + user_content.append({"type": "text", "text": request.user_prompt}) + + user_message = { + "role": "user", + "content": user_content + } 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} - ) + # 纯文本消息:content 是字符串 + user_message = { + "role": "user", + "content": request.user_prompt + } + messages.append(user_message) return messages except Exception as e: - print(f"进程 {self.process_id} 构建模型推理message: {str(e)}") + logger.error(f"进程 {self.process_id} 构建模型推理message失败: {str(e)}", exc_info=True) return None def text_prompt_inference(self, request: TextInferenceRequest): @@ -375,13 +376,28 @@ class Models_Server: temperature = request.temperature, top_p = request.top_p, system_prompt = request.system_prompt, - stop = request.stop + stop = request.stop, + image_data = None, # 纯文本推理,没有图像 + json_schema = request.json_schema # 传递 JSON Schema ) - messages = self._build_message(inference_request,self.model_server_config) + messages = self._build_message(inference_request, self.model_server_config) + + # 检查 messages 是否为 None + if messages is None: + logger.error("构建消息失败:_build_message 返回 None") + raise HTTPException(status_code=500, detail="构建推理消息失败") + + logger.info(f"构建的消息: {messages}") inference_start_time = time.time() - # 构建提示 - prompt = f"USER: {request.user_prompt}\nASSISTANT:" + # 准备 response_format 参数(如果提供了 json_schema) + response_format = None + if request.json_schema: + response_format = { + "type": "json_object", + "schema": request.json_schema + } + logger.info(f"使用 JSON Schema 约束输出格式") # 生成响应 output = self.model.create_chat_completion( @@ -389,8 +405,9 @@ class Models_Server: max_tokens=request.max_tokens, temperature=request.temperature, top_p=request.top_p, - stop=request.stop , - stream=False + stop=request.stop, + stream=False, + response_format=response_format ) inference_time = time.time() - inference_start_time @@ -406,7 +423,10 @@ class Models_Server: } } + except HTTPException: + raise except Exception as e: + logger.error(f"推理失败: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"推理失败: {str(e)}") def multimodal_inference(self,request: MultimodalInferenceRequest): @@ -445,6 +465,15 @@ class Models_Server: len_images = len(request.image_data) # print(f"full_prompt: {messages}") + # 准备 response_format 参数(如果提供了 json_schema) + response_format = None + if hasattr(request, 'json_schema') and request.json_schema: + response_format = { + "type": "json_object", + "schema": request.json_schema + } + logger.info(f"使用 JSON Schema 约束输出格式") + # 生成响应 output = self.model.create_chat_completion( messages=messages, @@ -452,7 +481,8 @@ class Models_Server: temperature=request.temperature, top_p=request.top_p, stop=request.stop, - stream=False + stream=False, + response_format=response_format ) inference_time = time.time() - inference_start_time @@ -491,6 +521,15 @@ class Models_Server: len_images = len(request.image_data) # print(f"full_prompt: {messages}") + # 准备 response_format 参数(如果提供了 json_schema) + response_format = None + if hasattr(request, 'json_schema') and request.json_schema: + response_format = { + "type": "json_object", + "schema": request.json_schema + } + logger.info(f"使用 JSON Schema 约束输出格式") + # 生成响应 output = self.model.create_chat_completion( messages=messages, @@ -498,7 +537,8 @@ class Models_Server: temperature=request.temperature, top_p=request.top_p, stop=request.stop, - stream=False + stream=False, + response_format=response_format ) inference_time = time.time() - inference_start_time @@ -527,27 +567,51 @@ model_router = InferringRouter() # 支持自动推断参数类型 # # 依赖注入:获取单例模型服务 def get_model_server() -> Models_Server: """依赖注入:获取当前进程的单例服务实例""" + global _process_instance # 必须在函数开始处声明 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] + config_json_file = f"{project_root}/config/model_config.json" # ✅ 修正路径 + print("config_json_file: ", config_json_file) + + # 读取配置文件 + config_data = read_json_file(config_json_file) + if config_data is None: + logger.error(f"get_model_server: 无法读取配置文件 '{config_json_file}'") + raise HTTPException(status_code=500, detail=f"无法读取配置文件: {config_json_file}") + + # 检查 models_server 配置是否存在 + if "models_server" not in config_data: + logger.error("get_model_server: 配置文件中缺少 'models_server' 字段") + raise HTTPException(status_code=500, detail="配置文件中缺少 'models_server' 字段") + + # 如果 models_server 是列表,取第一个;如果是字典,直接使用 + models_server_data = config_data["models_server"] + model_server_config = models_server_data[0] if isinstance(models_server_data, list) else models_server_data 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 + # 保存到全局变量 + _process_instance = model_server else: model_server = _process_instance if not model_server.model_loaded: + # 重新读取配置以获取 model_server_config + config_json_file = f"{project_root}/config/model_config.json" + config_data = read_json_file(config_json_file) + if config_data is None or "models_server" not in config_data: + logger.error("get_model_server: 无法读取配置文件") + raise HTTPException(status_code=500, detail="无法读取配置文件") + models_server_data = config_data["models_server"] + model_server_config = models_server_data[0] if isinstance(models_server_data, list) else models_server_data model_server._wait_for_model_load(model_server_config) model_server.init_model(model_server_config["model_inference_framework_type"], model_server_config) @@ -603,8 +667,13 @@ class Models_Controller: @model_router.post("/text/inference", response_model=Dict[str, Any]) async def text_inference(self, request: TextInferenceRequest): """纯文本推理端点""" - - return self.model_server.text_inference(request) + try: + return self.model_server.text_inference(request) + except HTTPException: + raise + except Exception as e: + logger.error(f"端点处理失败: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"端点处理失败: {str(e)}") @model_router.post("/multimodal/inference", response_model=Dict[str, Any]) async def multimodal_inference(self,request: MultimodalInferenceRequest): @@ -679,13 +748,12 @@ def create_app() -> FastAPI: 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" + config_json_file= f"{project_root}/config/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] + config_data = read_json_file(config_json_file)["models_server"] + # 如果 models_server 是列表,取第一个;如果是字典,直接使用 + model_server_config = config_data[0] if isinstance(config_data, list) else config_data uvicorn.run( app="models_server:create_app", @@ -696,7 +764,6 @@ def main(): 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" @@ -751,7 +818,7 @@ def test_inference(inference_type=0): if __name__ == "__main__": - # main() + main() # test_inference(2) - logger.info("work finish") + # logger.info("work finish") diff --git a/scripts/ai_agent/tools/core/__pycache__/common_functions.cpython-310.pyc b/scripts/ai_agent/tools/core/__pycache__/common_functions.cpython-310.pyc index 8d4abe19060302c1a567e3385ba28f02a3f630a8..684d3b37d942fe83ddd4b21405586096fe96ff38 100644 GIT binary patch literal 3675 zcma)9-E$jP72kWcyV_M`S$5on&;V)*ID!^WI|I|2PQj1{%Cv651y3}9M%AvIb+o(6 z-Q95%q)dnt;)EExL%y4&_CP~Y8l1M7#7W%bAK;0{ec=JGEI;{$nGS&@oO9RuNG7zb zM|bbXx#ygF&+q*9*zV|v3Gn>vlP8UX>jmK-IB0*OF!(O~oWDY&2^uL1nea_A1je*T z%}6F2>(CM)*R6JfzQ?p=xL@nyI=Z#B zu(nnsLqdApGMq>lOv}Fbis87vxW&l3eq_KZ`^sx2*RZWz(U;!vN=4n5ermxuPjJWT zBf=Z6?)&g_{t3eptHdQUv_fV{mAY`?itx5@mW~r5ROqlk;pn0u z3gZzX3=2cTGlH{e4A$^(8~lc0eF)ZFvHb0D7Ies{LyMk`pq6b72{I~`Uv0`LTtXaezku4Xz=#A`k($#J2xFn{|=UG z$1c|<|H5_fCYag>Q~kV;iVH?j&z5rT_Wo2$6YQ&Bn+tC3Z;J6wsJ*qf-45T(ZLKU! zEbl!X%+J?O&IOlF1PA786UT#z6ZP2(D+`BK7LEkx_tnmw2@c*^K6)ycI2CFgv@M+* z@d>*ld2Zq9=hDPibSrObhBe}S4sN10F&!L!C%lT5v;C<-Zv(7?w))&$f2wxq*5dqR zFmXA&l~nH?((5JL1^8{HdcDVCvNr|7;A!p5q1v&D#(mB#1PfE%ruI!A&Z5ZK@#Ry~ zp$851-V-49uv4`YSA(gymX5yD@G7nPqFvH0UmSI8Xp44E^Q96qEZ6Va3PBjO-L1A~ zX)iHmGrtqhw?&7;>#Gy5>&!G92Qnp`P+!`W*Lf!S5yxd|(N~+X^hNCYDw@h_IXCAM z{i49|mhSwpZ@X>kecQd9HB#_)?C2YKIXf@{km_^t%qY1|U(U$p_=IcQMW-)sGaVY! z1YHHs%4706-zb&+1k-a`mfJvc9iZL$8ZvRM|`KGK>I(S$rJ;=#w#~!q`{Q<7^RVgFS}hC>zU~a1nWAg~o%DarkkDxB?yE z2Qp4Z$tVSu0G^nnL~tV&QlXUy=Zx=EMHhHtoNN((@yyR@RjSI6jnb%Gku_11CTTTV z5oe;6s3xx$W(l&y&UL^M(x_7Y0c0;wF377|v?2jdC=~_rS46Jh`KwlyJ<85?8h>N< z0>`Kg4$@T4K|}2vgmdYb_jhoe<370jn=nbjj9t8aHn?gRtQ>H~Z?GI1g}FurtUX88bDQ3o=%0S2x_8yX^a^#)df?m6PmQjzKDH9DEksNcm$sme95pheU~qSxS3IS zdH|9c5*W|Gu=zYnuP^D$!RtV)iaM#ri1Jm{5>+YI2bAwUSWST6w= z4g(iTz=ddAMlKBNE|iq9k`b%Y9;{>ddyK#?wY;-!59iNM-vhOe;{6TJ4HZf%I6PDT z_+zYYcwMt?;MG^cZ!hF`SUiM9O$d_~%m&zq(<)Y)H=NpxpUeJKOHUhE&>q+D6W#Tg z)wUlw5V)?bv}(5s%v}HNyQ`~z{Gfj2-O#h8iw~jKL#}8tQOgG`SuULn&RhVZ;#dVT zl)+IwXAXK_fApT(%=P-=eM|eU2e%I_&)#g-Tbwjeb?8si8ZVQ=+iV#QxVONHO;Y3x z?QoV0DN)3`^HnET(6hX5i5ZSzId0C%>%NE=lTlG2FH9IRHe}Mb;MF2Vo+F%ZbEfl1 zE)1&)9wq#?{0g=^n1k_-1Q7|PNvJ3`-j9h2k%^2WAVg(v0;mz+P)odsb}9P;+QsSs z`j}f`zsIMx1BFuA@nyGMf{Kz+T*O3UND2%Ilwqzh{IAT97VVJ{6vK}IdmbAsR`5gS zZZ8^dhD`AS&i7!0gd8R``q9P)oEJFTD=)yO&MWFK;(t0$KQszekkkpjl}P-1RHUTm YE+zCnrLlYT@drlTlqmPYwZy&u0x)m9*#H0l delta 277 zcmcaDbBHr3pO=@50SI=kJDj1%!octt#6bp3K#l_t7wZ6t6owSW9EKIW3%XQd{W6zhli_{N9%IO>NwhQ&L2>K7L!>-*-Xq~_>5dd53W z-pyjn$Tj&ri*c|B&@@ffTTJ=Ix0s77i*K>#ZkqBWR_ZEju aZhlH>PO2RvkXH=yJ_i#ABM>t2FarP=h(1dI diff --git a/scripts/ai_agent/tools/data_process/image_process/__pycache__/image_process.cpython-310.pyc b/scripts/ai_agent/tools/data_process/image_process/__pycache__/image_process.cpython-310.pyc index b4d3c671bccd0de6c740fb6cba5fe788dfffc930..33ce4b8db1997efcef774e88e05b701992874f87 100644 GIT binary patch delta 1131 zcmZXSPj4GV6u|e5ckT7YvA4G4#Ich&O-Q?>#ce1pgeX)XQWdHwf&DB(J{i4`}hcDKn6HyS=XK*@mrnxmy8_aT}uDNpp2KCc<(GYXb;0!<$)}TrM z0?+c?cTDY17sb*UxGipq2G5JqpxD*9#iyR@?lNEGMWx{-ah=;@h3w_QYS-k~c=@^E zzQ#3P8M4uuy9S?Xe2G^@PBiKo9m=U=b&W#J@EW<^$U}aPxBllAvPLN~t zfxI0~@to!KS9ssJWR&YgkNL1MO_H?wBd;g+xfgrV7|RmGxrPg5{QdLxVK5Ty!^HD@ z-Q?&fy`y`#-yln56Bx6g23aUV8O(3l6)ur-=bT;cfu`XyZLpF4r2h!z^lPKN*-(n> z3M&NHoLuaOiAxNh-=FA27%%PT*!lnMcJNSM8^?U)0gFs=f|y6uat(LjKX8QNwS+cc)F3sl}%gfp%)*@tQUsD=U7i)R@P5s zb{O<}0>|?w^-d5Br4c9LP{^FmJw)#q)$~m2pt>cE?lACTRL}dz%f~s4XVBcD+9pG> zO_Que+XORXQqNmpvH}#ClFeK}x0r3L>RBBaWdF4Es@}>MrWS$CGN@fKV6Xn-)n75B B0gwOy literal 5301 zcma)A-)|h(b)GxFc6PX2QY0mrk|VEd%huYFn8-h(vT15ckzG18rBb$p3<@lkdxy(i z?e1*v%t|IPG-6RojKqo!RB_rkO?KTVwNV?AgF=;MSw{bZzVxB@sX)0D`IIL=G(e?( z=gyLn9RckSymRlJbAQa-^L^(#8}#?P8m@mm^6&F&4{F+fQRCjL4~^&WA6{Xa#x>4r znwQ*}$5dPQblmltQ8zuaZh2PS_UwAfOVu6Esi(a(OYB|Gg`H9Bt7p87>N9Ko^{hA0 zdp_t5s`<8fL+U>44dZUrM(SI=tv&l~u-~rgcI|=ss5e^Q;qB;+-|6l2Gya3ito^l) z=I!!#RUhK1QO(QwySd}%mUX82tom^K5&u!NScjp#o2RKyJ#(vje7E`-cX{6hvUS^! z`+Gag%kzx?DDU?Ns@XY{XZgT6(|dwH$%m+i5BpE@5&!FWK2+T|=kTq3+d0eIkDYI? z>Gd&h47>X}f51=qkB({>lcG_h8fF^hJ7D<@zSG}6r}GE-*D&ul%NpPH9^((8C+BbH zyZr;Oe3(B1%cs&Ty@&7n`Uv*TKR|2m!Jbl#TcR3(h0 z`Q1t>lEy@1K{`j9Q6*>;YtnkT)vWo_dZmGOo=NjqF`CESnyeSgJ}UO{NK_i-ye^%? zrw@q=FZ;58qjp4yK&XksI4lP7}IC)g2NG?Bx@Kb#wz59vTe*}SvJI+ALw_)!{~he1AYA%&R#rH7JbfGcOw=V!$k=;(kdfVxcWtyitL`+WWcbL@8;R2|70Qv00)eG+dLib`! znIv{^on8Iem2p+4aXsfEddVddtiJQ3l~1p({mbR<&2wKi8_xk(xk*Aq_wpxeA9pq% ztIFEv7rXEN>h9F{ZeSDP%5wUP$FcA3=jYcupRT_3_R7t7dwW-luD&twi8WHuaS zgo!_;CHqAX_6F3$@|OVR;Q1!$N;DR~6=^tr1IN!|{WQ6XQH3*5Tfxa}WMcykLj-I_ zM%(1%DvE3=7Drawj?KubrT|4Na@y(Gf~6g~?LKbAb~Qt^OqQ|TBk%r&8IX4hvUX*;`=ig`g}=Y? z3%Kpf(c_6vt$p@k!u&lK`{Zgu5+5*V8Xz8LQBX$*sDF{eTo|BS{lewOosQDVZn1=F4_E+j<9B2VvP&$1;+O?ou_JvHHDT)TMkwLvq>2!2Bs09MC{^-=vnZltLW)2{v zCFGT!6G1)UDGOfGT<}turP7!Sq^T$_gC#wh*%w0WKqK$qiC>!&-=@uM4_nPI3 z#3PenAA0FoB`~lt+ClQrON;XCMzvX~B~?Km>5qJT(`65A8lHq*NZ~`*ZRVOo%r%Bs z)iZU$Tr!M42g=z{#~(t1>`h3+{+D3KWpih%shkfco0#Q?P(iQLxZ9966?FWN`Dm zKC1l{fB`_HVk@?n_1HLNiWg&Jqm4+Fikxa1AaY~4^@z62P_vTSoQ`LG+`3=@d@kyl zWybBYzT^OE{X7+$vC}cILVC%Kvh9J`1zQht2fOOyY3{~-YA1c8n!0Cjr}tvjE!Cm+ za7W|)zfARDIkIpO5QD^D+ydpbm1m=EF;?4nBLz)kDf3!}Bd$VYp{Ag@eE~VfJ>v%c zKjQ$;!PbA!SErbefU*}2uD-GQ`sEFzT?dbM&tF?@-{{_W>-RU_T)FxA%JrXifBF8( zwI8os`w4}PYaew#`rGc=_ti|vboUK8IQ;P(un+mCBFd^-NDZJpsLEF(O83q4-M9ZK z+1SeG9c(NSkJu76gSasrH2l5yqIvb_SMzEr@n?8D@f{S?g4_$jqQBCJe9;VQMUwFA z$I!cI?yok>;u*4~DBu}ru9Hj_R6u)}Bu0_|WG1TcOod1D(w_G#<@w0l5{L=_T&!(U zl}wXh4 zq!rE=o4z!FB0(xc?4@16eep6eeblOvsVRmrl-1zI>!WQMB-vuS`r$zWjX& z0hEM76&l62X#Ahhc)U!J`@*x-u00zJ7y2bZWF##xQ6L1eTrDA_elbb)m#Cnv3KBZv zdnhEEm+TDoSSr?hFO#fQXck4WE?EJ>T}4<@Pvu@5y)PMM?~)Nm;`J(K4(a9fOhzAJ z8L-fhk%huFnGUUMGW&nBsUgH*-O$nF;Ll_(bN)N)?qa3^mb#5Q@ziZGhiM>&a}a7` z9(8d5h3r>inE1C)t4Goyic)O+G@h17&~02_ir*#?mEV(igOGTAUzK=Ei0NWS45|gDY>W-$B1CO~5~ZfDIiy<~xko*kQt^4gm`tW^8t9`XUxVkK1kOuwtv)XV_iPQHX6>xgFb8 zdqucdr6ste$6AAW9Ge+!72cod%gWQuSr(V|lzBJi`*lv@d0_F%9>&|K_vNWqAD-_* zJrywLN_|e9I#G}4-MU0A%Yv@xb^owmGD@aqRvq=zA4*|VwQdm1eED`WaXGJY8z1%Sa4+ zLa{h8+!qSQ3FlD;iO>~|hayTKO!x;9(XdQ-p8{%;P4IjjV7?EyI!18rB##S&f>5@TNk4+P>w+#iU^Tblf_I3`9c z#}ilyX5Edh4w>N3!GG}^s7}GBcLUXmmrj0DsX;!ZAFRT38+5VPsB?K*gSrL1xaR_A3# z;uOxq^)Y>D8&{3`7?&Zj0mft|p3rvd+Fzf$Hh%2d3&%5mJbmqr*RM|gY3|&s&~tV2 z-M#I5GA~}r{Cbpd-kmK82OXxiHI2;KH|O4Z&F$=TiNvTGBj){EntJ76q*o?p817e3 zmKY*YSs`pFLQE}#aXHrBMNGkna#+T3Vs1SWlvNKQ;)8*3LT<%4f(ZwQ=rUmX@k9(! z|M2$);sL^|JYnP*Jr5@o(%o<%G9Wh`NCcGrzQmzJ4W4$tr(ag$4Y42&MdPuCK*$dU zpd%iMgkufVkUxqeL2#u8&GxdbjnP3;qJ51&>sqQdcEZ#YpJFmpLukRk^X{3_igan+ z<BMj^_v-s zV-YbhzHsGKMZ;U%gy+x3^GBx2x26i4r-bJ37B~=o8(Rr8J?61{O3;TTqNkbruxf>; ziTkjL2Od{}kXRI1?)76i7CI^uOFV%4<+z_Vg4E~rLJ>fXfk|Lafmg22N&@u={N1}W zUa}G&+ntLJZ!QY5LLkeI+C$Ssg0;60la&ZY*Et-Gj zc;@W!YbT%3yz=UMugwoVmU;Kt+4E;IFFiST>6y&qug$%FZ1&B^D9H522cvSoYW7Ox zUYChN1l=A?{hEkgITjd*hC@m}G41hm?%3BQ5mt#3K7esxkeK?ykwAP?BlslUZM=q7 zHFTt)d5{v#c_gl;{q`u9vu@?r+~#)Zz6<`bHmHWs*G43iOxsIOTc5Iyb-&?C*KbeO zZ%@~^CF|Q#_MOB0Ra?>Mk>4B{1aOV2zAtMDQHE z&;8|M=F;)JirWb&=xnF>mAUxN+?!`qkvRAMvzZrP(o8L#B!|J`jdif3%=?eepLuU~ z^yJk`FJ_mf+MgYMPHSp5G2H}W=-Au3bABJN7g$WM7GioN5eSFkgWvLrc;AMtE)lPS zZq?s$Ddi}rXs%<31ptkU=_m@}vZ6w`01$GRVlpvmFzDhi#XSvhg#C%WJ{ilsG3s2f zDVpDxP~6w{CmzY-CcYaqcECUO2vmTWWk@JE_RBA9VQ&nxL;+<30omt2nJDRfB zr|peNd*cLvX7e!rl@ZxjJUIwHZ!n+eIMy+IWZXEx4s|3An`SIUX-marOU2l(bmfL* z<%U#cL&|bT+HzOYa@V9OWx03Ca4)7e9w|@(l7_rLJP;?sK;VdqkGKh>K?;m|$t4O7 z_%fO(K*&I}+%jZxj{g6IjKcU>Jp@(oWtIyfAGah31$}{ex?HlMgRc|T?ahTyevHJ? z;|O>m>~k=2ivU}GJR=zl)Hb{@ZY~nPnR){hh4=Bxh2BF&Ug~@|6+LJr13d`INb_wU zuP|LZZt)E!_e1av;ISa5=yy|*1U%I+N|@> zK0kNyqTA^zy9w2ZIes89uwMy;!kBIVrbr65X->*1@}tC{W=MQA5R1cOBnEeWTtmV# zs!e?fZ=n@MLku@1!dq$8OsjIJ2p`2EC5|hAcL{kw#Po59x6z6s3Vn_Q5B1|nLg|J4 zSj9?APd-M&7~oPZ8fI23gPAHe0Z`#iP^YjSYl2G}u+zAPcT8LCBip_xshTNsPOa%k zmF=Fg@4o7&I^Fqn=eRrN*f`ueYgu{4S~S`<(lxg0?8E80=44%Sy3UiV^L%D)Sui4y zxkub%t5cSBGvzfeSG`a*o;Y{(%+ZP4CK|_oHOap(z9W9nl`4N=xMSK{mbO+Vt(9ZG zixn3t{!lf6lP>oZ?Yeu~v3jI+x}bP;-^jkPO{s#_(~k1f_dj+2cwNe|ex&tER9Nc4FPIm=xSIw$ik%`m(Kh+<4A*#+Gv4 znX)yeZOutr^W?getz}AR`Q|GtvX(9)mUqku1ylCgv(ovZS05bTHC4FbGhyS5t>n7} z9)#KCxwX8t5`9!Dwwbt()~;w{xxceK@GiE^#m?`At;Joi$u73D)5Ujo(oQEJ8Qu$> zxRFZg2^)#Q!(=dqsizPlB$SXYVv2XzKr0Gem}Xm;-XNHsGckoD{rzfIN%OLBXg{W@ z5aHm&F?}d%*D>`q!Yhe^=pd$-0^x#(8;Rie_l3f;-%m`Ct46|yWl|VSU<^+gzxK8v zrf7gRW5f!%=0Q0a_v1(e-rsaGVhlv#;nk~pDzQMK5en{zD1D)RVrcJd>mp{j-}k`s zWK5@`k8GIANEa+)?4-*u?1vW<;p6JV-K6xY$uOpgtQIKrA|bq*8>@bL0jYPb=GDSa zA?Olec{@G`YBa-)-2*keXc%TuK#ce~f}i=H$el*+FHzw?(8>&|`5YB|j*1rx5o5V7 zpyINV2cB7-E?So?T9+zv4HaIoRi$l^0oRQOlD73j)&&tUh7-au;e_p&?J4f$o|Jvf zXQ*buiWnh{N-m?4X|ZVZ_L1ACEnEJ@a}2v6Ah9TIUUk{LYHVxDTsvf35KN4Fe8&O; za=!gy*M+XHD0{uw%s9r{U+#LoYXJc}S*wz%2Oiewp@H 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 - +# Removed ROS specific functions (ros_image2pil_image, ros_image2dict) as they depend on rospy/cv_bridge 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): diff --git a/scripts/ai_agent/tools/memory_mag/rag/__pycache__/__init__.cpython-310.pyc b/scripts/ai_agent/tools/memory_mag/rag/__pycache__/__init__.cpython-310.pyc index f557d9065fe3b386d91fff92018f878e05c49a18..c56b8dc4d41787606dbd56e964607a33f4e96adc 100644 GIT binary patch delta 40 ucmcb>xSWwEpO=@50SLmrD`!sRu@>;s&&bbB)z2tR%u7!z&CZ_K5C8z!*bGnr delta 81 zcmZ3^c!7~8pO=@50SNTu4rWZ`v9>JM&&bbB)h|sd%_}L@56>^kE-pw+PSp=6%Fjwo jE-BUz^YM)j^KsM+Tvx diff --git a/scripts/ai_agent/tools/memory_mag/rag/__pycache__/llama_cpp_embeddings.cpython-310.pyc b/scripts/ai_agent/tools/memory_mag/rag/__pycache__/llama_cpp_embeddings.cpython-310.pyc index 3d78fa6653291be95d81580f9e0399c246dde307..b032b34722ad167b4b1d10d85e527110f35843ad 100644 GIT binary patch delta 67 zcmZp7zvsr2&&$ij00d#*l`}W;__7Lw>1X8Urs`*uCg!E5m1bvep2RwjiBEu$g^`Jo V<3Gzk7RD-*$@$!po5MK_qydj!68iuE delta 108 zcmccT*6z-e&&$ij00ecHKW1#?@nyBF*U!k$P1P?=D$Oe?)ep}v$}TQQOitAgD9X=D zO)e?c5A*Sj5A$)<4|NQScl6XRE=tz-%}+_q*__EbkBN_uk?B7R6B8rHe~y1uCX*-d LNp9BTFpvfSt{)>H diff --git a/scripts/ai_agent/tools/memory_mag/rag/__pycache__/rag.cpython-310.pyc b/scripts/ai_agent/tools/memory_mag/rag/__pycache__/rag.cpython-310.pyc index 791247fe56e5292ae0aae5761e4aee25ad29f981..2b3dfdc11720cf7f67ad4362165ef923e12fc678 100644 GIT binary patch delta 4210 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 Wiu7twQod3{<3HQ delta 2064 zcmZ8iTWl0n7@l+H%w;d#c6*_>?QMI34cLNM3IaADA|YBRB?VT?x(q zY)e9k(byc<}G}m zzd#E8w+SL9+ayh1Ev^y`{u{^@xBcItPH`=;1Dz7Z!S$0V4D$OI0b=Q^hhZ^Z#gzdx zfE`59G-4H8b>RFIPvbo1gu?1=Wd@&wGdp;n(xdECC>)z8TpQ~^@D)fI4|9Fipc*$F z^`xgQzi^*&05tRJL|Byu9;uF^Q6;8}>QHNE46`lZK3;4M9kwXV*nNrgh@Ik@tUXc- zg85G_&c8DI^VO^Kub=+qrPpr0@zKrc^YfpI`ZUW9)pv<^i=wqLACuW*I{;kWI-be# z94nBVX8^>7Kv{sW7P*$L#Fyc=sqI%DO)}11I|)9&cR~5-9ZD31&AO8q)*9f4%wz0Dm#etdfsphM?Ipj z21lDkjt*(l2UJcR<0PW^9aRSYXpn2OI#s#hXfhp?I2cfKR6u>kkUEYgQ%RIgZjSXp z7EQ=vF_`(N!TpZ;Ad7vFM_p#p%$pM?q|tkOY4nZ!p@ZK zvbk#xSv*OTJQ**_Wri|?NtWSbiCiknEU{3zPW>Cj#OGCYlU=buJo2!Hm1L>@#ApVB z?PK}A`v#0zOQl}jXTy67+G8qCU#4Dy_-g2dGp&pc<8iLa{5+r9?FoZK51 zg)mWr$VAYBaO=oiz$c=*jzYK;6)l!gM$|>7W1h2eu=xP70eHob8%*XB%;GF#9kUp8 zf(2|gAZ|n&CVvMqrvsSSL_x%cfK17*y?;rW29GzlRsiA6D+H4X!JHv5eTbua{i}KN z#w!X(gHc1eVk+AR7OX?UdI0df)F0zv2xkXP9HWQp(-}LH>$hl(c+-XLlm;@;j7ls0 zf7`Gv;&RQ(=wBeq6$21`v9Sn&A$HHN63lF1rxej0tU!B2eQn~YX_D8+d!c?2Ev6+I zL`$bp-k@c9(=leTW6q(x&+)MnT%9F!nPWormUGSVc{N=jzNoF9taSXes$2m{^^EtK zo^Hem3>Z|QHK$;NgRJ%;SslnsDD!&SFb7>RgT0Q~YinTXr_sg1Wk+DV1;)-oV+5fw zia-{mjl94KK4=U%-o$~rvOtp@jTE{m)_iHK?oAYhkUbh>Z}=>hoU8(NL~L*P!7t;% z1_4~r7Ohz&!|Ii<>$B{UA&Vus#STlRSL$k?#a&}G$!r*uf=BnP@O;P=vh8vc-%>ew z<<8QQi88ED{1&az4FrkVFT#!OL|?!G@mymgnh?W{YfN?k6l17Myx*9Ge@A)KQNWj* zwgO&nvi(WvK(>)9wlyDbm46!ctOTzhdL<=?8cRvYNRajFC7Vr%Uz=Odc@b_I?}&!V saIyTMkp-xXdl=)py6Rsr!?*~`&Ve;n@j{6B|A{o4h8gy6^&@icUos5zKL7v# diff --git a/scripts/ai_agent/tools/memory_mag/rag/rag.py b/scripts/ai_agent/tools/memory_mag/rag/rag.py index 77a2241..da053cf 100644 --- a/scripts/ai_agent/tools/memory_mag/rag/rag.py +++ b/scripts/ai_agent/tools/memory_mag/rag/rag.py @@ -101,15 +101,16 @@ def set_embeddings(embedding_model_path, model_config:Optional[dict[str,Any]]): if "llamacpp_embeddings" == embedding_type: + # 使用 .get() 方法提供默认值,确保即使配置文件中缺少某些字段也能正常工作 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"] + n_ctx=model_config.get("n_ctx", 512), + n_threads=model_config.get("n_threads", 4), + n_gpu_layers=model_config.get("n_gpu_layers", 0), # RTX 5090建议设20,充分利用GPU + n_seq_max=model_config.get("n_seq_max", 128), + n_threads_batch=model_config.get("n_threads_batch", 4), + flash_attn=model_config.get("flash_attn", True), + verbose=model_config.get("verbose", False) ) elif "huggingFace_embeddings" == embedding_type: embeddings = set_embeddings_huggingFace( @@ -156,19 +157,49 @@ def load_vector_database(embeddings, 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: + if not os.path.exists(path=persist_directory): + logger.warning(f"向量数据库目录不存在: {persist_directory}") return None + + # 先检查集合是否存在 + try: + import chromadb + client = chromadb.PersistentClient(path=persist_directory) + collections = client.list_collections() + collection_names = [col.name for col in collections] + + if collection_name not in collection_names: + logger.warning(f"集合 '{collection_name}' 不存在于数据库中。") + logger.info(f"可用的集合: {collection_names}") + # 尝试查找有数据的集合 + for col in collections: + if col.count() > 0: + logger.info(f"发现非空集合: {col.name} (count: {col.count()})") + return None + + # 检查集合是否有数据 + target_collection = client.get_collection(name=collection_name) + doc_count = target_collection.count() + if doc_count == 0: + logger.warning(f"集合 '{collection_name}' 存在但为空 (count: 0)") + logger.warning(f"⚠️ 该集合没有数据,RAG 检索将返回空结果") + # 仍然返回数据库对象,允许后续操作(如添加数据) + else: + logger.info(f"✅ 集合 '{collection_name}' 包含 {doc_count} 条文档") + except Exception as check_error: + logger.warning(f"检查集合时出错: {check_error},继续尝试加载...") + + # 加载向量数据库 + vector_db = Chroma( + persist_directory=persist_directory, + embedding_function=embeddings, + collection_name=collection_name + ) + logger.info(f"✅ 成功加载向量数据库 from {persist_directory}, 集合: {collection_name}") + return vector_db + except Exception as e: - logger.error(f"vector_database加载失败: {str(e)}") + logger.error(f"vector_database加载失败: {str(e)}", exc_info=True) return None #设置文本分割器 @@ -206,7 +237,7 @@ def set_document_loaders(self): def retrieve_relevant_info(vectorstore, query: str, k: int = 3, - score_threshold: float = 0.2 + score_threshold: float = None ) -> List[Dict[str, Any]]: """ 检索与查询相关的信息 @@ -214,7 +245,8 @@ def retrieve_relevant_info(vectorstore, 参数: query: 查询文本 k: 最多返回的结果数量 - score_threshold: 相关性分数阈值 + score_threshold: 相关性分数阈值。如果为 None,则使用自适应阈值(前 k 个结果中最高分数的 1.5 倍) + 对于 L2 距离,分数越小越相似,阈值应该是一个较大的值 返回: 包含相关文档内容和分数的列表 @@ -223,21 +255,41 @@ def retrieve_relevant_info(vectorstore, # 执行相似性搜索,返回带分数的结果 docs_and_scores = vectorstore.similarity_search_with_score(query, k=k) + if not docs_and_scores: + logger.info(f"检索到 0 条结果 (k={k})") + return [] + + # 如果没有指定阈值,使用自适应阈值 + # 对于 L2 距离,取前 k 个结果中最高的分数,然后乘以一个系数作为阈值 + if score_threshold is None: + max_score = max(score for _, score in docs_and_scores) + # 使用最高分数的 1.5 倍作为阈值,确保包含所有前 k 个结果 + score_threshold = max_score * 1.5 + logger.debug(f"自适应阈值: {score_threshold:.2f} (基于最高分数 {max_score:.2f})") + # 过滤并格式化结果 results = [] for doc, score in docs_and_scores: - if score < score_threshold: # 分数越低表示越相似 + # 对于 L2 距离,分数越小越相似 + # 如果阈值很大(> 1000),说明是 L2 距离,使用 < 比较 + # 如果阈值很小(< 1),说明可能是余弦距离,使用 < 比较 + if score < score_threshold: results.append({ "content": doc.page_content, "metadata": doc.metadata, "similarity_score": float(score) }) + else: + logger.debug(f"文档因分数 {score:.2f} >= 阈值 {score_threshold:.2f} 被过滤") - logger.info(f"检索到 {len(results)} 条相关信息 (k={k}, 阈值={score_threshold})") + threshold_str = f"{score_threshold:.2f}" if score_threshold is not None else "自适应" + logger.info(f"检索到 {len(results)} 条相关信息 (k={k}, 阈值={threshold_str})") + if results: + logger.debug(f"相似度分数范围: {min(r['similarity_score'] for r in results):.2f} - {max(r['similarity_score'] for r in results):.2f}") return results except Exception as e: - logger.error(f"检索相关信息失败: {str(e)}") + logger.error(f"检索相关信息失败: {str(e)}", exc_info=True) return [] def get_retriever(vectorstore,search_kwargs: Dict[str, Any] = None) -> Any: