【Py】接口签名校验失败可能是由于ensure_ascii的问题

本文探讨了在客户端使用requests库与服务端基于Flask的交互中,请求签名校验失败的问题。问题源于客户端和服务端在序列化JSON数据时对ASCII编码的处理不一致。当确保非ASCII字符正确处理时,签名会因编码差异导致不匹配。解决方案是确保在签名和发送请求时使用相同的编码规则,如同时设置`ensure_ascii=False`。通过统一序列化规则,可以避免签名校验失败的问题。

项目场景:

客户端:requests
服务端:Flask

客户端发请求前对请求签名,服务端对签名进行校验


问题描述

服务端签名校验失败,即生成的签名与客户端的不一致


原因分析:

假设我们的请求体为

data = {
   
   
    "a": "你好"
}

在对字典数据进行json序列化时使用了如下代码

json.dumps(data, ensure_ascii=False)

其中的ensure_ascii参数是控制是否在序列化时使用ascii编码,默认为使用。效果就是中文字符全部变为unicode编码,这是因为中文字符不在ascii编码中。


                
class AnalysisSummaryFileTool(BaseTool): """校验文件""" # 名称 name: str = "analysis-summary-file-tool" # 描述 description: str = """ 这是一个用于解析概要需求表的工具。 注意以下参数限制: 1. summary_requirement_file_path为必传参数,表示概要需求表的路径; 注意以下参数校验: 1. summary_requirement_file_path:必须要指向一个存在的Excel文档; """ def _run(self, config: RunnableConfig, summary_requirement_file_path: str = "", *args: Any, **kwargs: Any) -> Any: # 记录工具开始运行的信息 logging.info("AnalysisSummaryFileTool start") # 获取机器人ID robot_id = config['metadata'].get('thread_id') # 获取Autosar变量 autosar_var_in_robot = get_autosar_var(robot_id) # 语言 _ = get_text() # 文件存在性校验 if not os.path.exists(summary_requirement_file_path): return _(f"找不到概要需求表文件路径:{summary_requirement_file_path}") # 获取大于等于第二行的数据(第一行是表头) original_excel_data = get_original_excel_data(summary_requirement_file_path, "概要需求", 1) summary_requirement_config = generate_to_dict( list(map(lambda item: {'概要需求': item['概要需求'], '可能值': item['可能值']}, original_excel_data))) res = json.dumps(summary_requirement_config, ensure_ascii=False) # Arxml文件生成成功,更新Autosar变量 autosar_var_in_robot.step_is_finish = True set_autosar_var(robot_id, autosar_var_in_robot) # 返回成功信息,并提示ai执行后续操作 return _(f''' 注意以下要求: 1.概要需求表内容如下: {res} **一定要把概要需求表的所有内容,一起发给用户确认是否正确!用户确认完之后,才能继续后续流程。** 2.目前工具只支持Vector、EB的产品类型,如果"概要需求表"中的"CP工具"不是Vector和EB,必须提醒用户当前只支持Vector和EB 3.根据概要需求表中的项目类型字段,选择对应的工具。如果项目类型为ZCT,则使用ZCT相关工具 ''') ------------------------------------------- 以上是AnalysisSummaryFileTool类的源码,请问怎么修改,能够保证使用Cython把这个py文件打包成pyd之后,不报“TypeError: Can't instantiate abstract class AnalysisSummaryFileTool with abstract method _run”的错误
最新发布
11-26
可是我觉得你改的 一点也不靠谱呢 1.# E:\AI_System\agent\model_manager.py import os import sys import logging import json import hashlib import gc import time from pathlib import Path from typing import Dict, Any, Optional, Tuple, List from utils.path_utils import normalize_path, is_valid_hf_id class ModelManager: """AI模型管理器 - 完整修复版(星型架构适配版)""" MODEL_REGISTRY_FILE = "model_registry.json" DEFAULT_MODEL_PATHS = { "TEXT_BASE": "local_models/text_base", "TEXT_CHAT": "local_models/text_chat", "IMAGE_MODEL": "local_models/image_model" } def __init__( self, config: Dict[str, Any] = None, cache_dir: str = "model_cache", use_gpu: bool = True, max_models_in_memory: int = 3 ): # 配置日志 self.logger = logging.getLogger("ModelManager") self.logger.setLevel(logging.INFO) if not self.logger.handlers: handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) handler.setFormatter(formatter) self.logger.addHandler(handler) self.logger.info("🚀 初始化模型管理器...") # 初始化参数 self.config = config or {} self.cache_dir = normalize_path(cache_dir) # 使用路径规范化 self.use_gpu = use_gpu self.max_models_in_memory = max_models_in_memory # 确保缓存目录存在 os.makedirs(self.cache_dir, exist_ok=True) # 加载或创建注册表 self._persistent_registry = self._load_or_create_registry() # 已加载的模型 self.loaded_models: Dict[str, Any] = {} # 自动注册默认模型 self._register_default_models() self.logger.info(f"✅ 模型管理器初始化完成 (GPU: {'启用' if use_gpu else '禁用'})") self.logger.info(f"已注册模型: {list(self._persistent_registry.keys())}") # 星型架构协调器引用 self.orchestrator = None def set_orchestrator(self, orchestrator): """连接中枢协调器""" self.orchestrator = orchestrator def is_healthy(self) -> bool: """健康检查""" return len(self._persistent_registry) > 0 def get_status(self) -> dict: """返回模块状态""" loaded_models = [model for model, info in self._persistent_registry.items() if info.get("status") == "loaded"] return { "status": "running", "loaded_models": loaded_models, "total_models": len(self._persistent_registry), "cache_dir": self.cache_dir } def _load_or_create_registry(self) -> Dict[str, dict]: """加载或创建模型注册表""" try: registry_path = Path(normalize_path(self.MODEL_REGISTRY_FILE)) # 路径规范化 if registry_path.exists(): with open(registry_path, 'r', encoding='utf-8') as f: registry = json.load(f) self.logger.info(f"📋 成功加载模型注册表: {registry_path}") return registry self.logger.warning(f"⚠️ 模型注册表不存在,创建新文件: {registry_path}") with open(registry_path, 'w', encoding='utf-8') as f: json.dump({}, f, indent=2) return {} except Exception as e: self.logger.error(f"❌ 处理模型注册表失败: {str(e)}") return {} def _register_default_models(self): """注册配置文件中的默认模型""" model_settings = self.config.get("model_settings", {}) # 合并默认路径和配置路径 default_paths = { **self.DEFAULT_MODEL_PATHS, **{ name: info.get("path", self.DEFAULT_MODEL_PATHS.get(name, "")) for name, info in model_settings.items() } } # 注册模型 for model_name, model_path in default_paths.items(): if model_name not in self._persistent_registry: abs_path = normalize_path(model_path) # 路径规范化 model_type = model_settings.get(model_name, {}).get("type", "text") self.register_model(model_name, abs_path, model_type) def _save_registry(self): """保存模型注册表""" try: registry_path = normalize_path(self.MODEL_REGISTRY_FILE) # 路径规范化 with open(registry_path, 'w', encoding='utf-8') as f: json.dump(self._persistent_registry, f, indent=2, ensure_ascii=False) self.logger.info(f"💾 模型注册表已保存: {registry_path}") return True except Exception as e: self.logger.error(f"❌ 保存模型注册表失败: {str(e)}") return False def register_model( self, model_name: str, model_path: str, model_type: str = "text", adapter_config: Optional[dict] = None ) -> bool: """注册新模型""" # 检查模型是否存在 exists, is_local = self._check_model_exists(model_path) if not exists: self.logger.error(f"❌ 模型路径不可访问: {model_path}") return False # 计算校验和 checksum = "unknown" if is_local: try: checksum = self._calculate_checksum(model_path) except Exception as e: self.logger.warning(f"⚠️ 无法计算校验和: {str(e)}") checksum = "error" # 添加到注册表 self._persistent_registry[model_name] = { "path": model_path, "type": model_type, "status": "unloaded", "checksum": checksum, "last_accessed": time.time(), "adapter": adapter_config, "is_local": is_local } self.logger.info(f"✅ 模型注册成功: {model_name} ({model_type})") self._save_registry() return True def _check_model_exists(self, model_path: str) -> Tuple[bool, bool]: """检查模型路径是否有效""" # 如果是HuggingFace模型ID if is_valid_hf_id(model_path): # 使用路径工具验证 self.logger.info(f"🔍 检测到HuggingFace模型ID: {model_path}") return True, False # 检查本地路径 abs_path = normalize_path(model_path) # 路径规范化 if os.path.exists(abs_path): return True, True # 尝试相对路径 if os.path.exists(model_path): return True, True return False, False def _calculate_checksum(self, model_path: str) -> str: """计算模型校验和""" abs_path = normalize_path(model_path) # 路径规范化 if os.path.isdir(abs_path): sha256 = hashlib.sha256() key_files = ["pytorch_model.bin", "model.safetensors", "config.json"] for root, _, files in os.walk(abs_path): for file in files: if file in key_files: file_path = os.path.join(root, file) with open(file_path, 'rb') as f: while chunk := f.read(8192): sha256.update(chunk) return sha256.hexdigest() # 单个模型文件 with open(abs_path, 'rb') as f: return hashlib.sha256(f.read()).hexdigest() def load_model(self, model_name: str, force_reload: bool = False) -> Tuple[bool, Any]: """加载模型到内存""" if model_name not in self._persistent_registry: self.logger.error(f"❌ 模型未注册: {model_name}") return False, None model_info = self._persistent_registry[model_name] model_path = model_info["path"] abs_path = normalize_path(model_path) if model_info.get("is_local", True) else model_path # 路径规范化 # 如果模型已加载且不需要强制重载 if model_name in self.loaded_models and not force_reload: self.logger.info(f"📦 模型已在内存中: {model_name}") model_info["last_accessed"] = time.time() return True, self.loaded_models[model_name] # 检查内存占用 if len(self.loaded_models) >= self.max_models_in_memory: self._unload_least_recently_used() # 实际加载模型 try: self.logger.info(f"🔄 加载模型: {model_name} ({model_info['type']})") model_type = model_info["type"] if model_type == "text": model = self._load_text_model(model_info, abs_path) elif model_type == "image": model = self._load_image_model(model_info, abs_path) elif model_type == "audio": model = self._load_audio_model(model_info, abs_path) else: self.logger.error(f"❌ 不支持的模型类型: {model_type}") return False, None # 更新状态 self.loaded_models[model_name] = model model_info["status"] = "loaded" model_info["last_accessed"] = time.time() self._save_registry() self.logger.info(f"✅ 模型加载成功: {model_name}") return True, model except ImportError as e: self.logger.error(f"❌ 缺失依赖库: {str(e)}") return False, None except Exception as e: self.logger.error(f"❌ 模型加载失败: {model_name}, 路径: {abs_path}, 错误: {str(e)}") model_info["status"] = "error" return False, None def _load_text_model(self, model_info: dict, model_path: str) -> Any: """加载文本模型""" try: from transformers import AutoModelForCausalLM, AutoTokenizer except ImportError: self.logger.error("❌ transformers库未安装") raise RuntimeError("transformers not installed") self.logger.debug(f"🔧 加载文本模型: {model_path}") device = "cuda" if self.use_gpu else "cpu" try: tokenizer = AutoTokenizer.from_pretrained(model_path, cache_dir=self.cache_dir) model = AutoModelForCausalLM.from_pretrained( model_path, cache_dir=self.cache_dir, device_map=device if self.use_gpu else None ) return { "model": model, "tokenizer": tokenizer, "info": model_info } except OSError as e: self.logger.error(f"❌ 加载失败: 请检查路径 '{model_path}' 是否正确") fallback_path = self._try_find_model_path(model_path) if fallback_path: self.logger.warning(f"⚠️ 尝试备用路径: {fallback_path}") return self._load_text_model(model_info, fallback_path) raise except Exception as e: self.logger.error(f"❌ 加载过程中发生意外错误: {str(e)}") raise def _try_find_model_path(self, original_path: str) -> Optional[str]: """尝试找到备用模型路径""" # 1. 检查项目内的模型目录 project_models = os.path.join(os.getcwd(), "local_models", os.path.basename(original_path)) if os.path.exists(project_models): return project_models # 2. 检查缓存目录 cache_path = os.path.join(self.cache_dir, "models", os.path.basename(original_path)) if os.path.exists(cache_path): return cache_path # 3. 尝试父目录 parent_path = os.path.join(os.path.dirname(os.getcwd()), os.path.basename(original_path)) if os.path.exists(parent_path): return parent_path return None def unload_model(self, model_name: str = None) -> bool: """卸载模型""" if model_name is None: self.logger.info("卸载所有模型") for name in list(self.loaded_models.keys()): if not self._unload_single_model(name): self.logger.error(f"❌ 卸载模型失败: {name}") return True return self._unload_single_model(model_name) def _unload_single_model(self, model_name: str) -> bool: """卸载单个模型""" if model_name not in self.loaded_models: self.logger.warning(f"⚠️ 模型未加载: {model_name}") return False try: # 显式释放模型资源 model_data = self.loaded_models[model_name] if "model" in model_data: del model_data["model"] if "tokenizer" in model_data: del model_data["tokenizer"] # 删除引用并调用垃圾回收 del self.loaded_models[model_name] gc.collect() # 如果使用GPU,额外清理CUDA缓存 if self.use_gpu: try: import torch torch.cuda.empty_cache() self.logger.debug("♻️ 已清理GPU缓存") except ImportError: pass # 更新注册表状态 if model_name in self._persistent_registry: self._persistent_registry[model_name]["status"] = "unloaded" self._save_registry() self.logger.info(f"🗑️ 模型已卸载: {model_name}") return True except Exception as e: self.logger.error(f"❌ 卸载模型失败: {model_name}, 错误: {str(e)}") return False def _unload_least_recently_used(self): """卸载最近最少使用的模型""" if not self.loaded_models: return # 找到最近最少使用的模型 lru_model = None lru_time = float('inf') for model_name in self.loaded_models: last_accessed = self._persistent_registry[model_name].get("last_accessed", 0) if last_accessed < lru_time: lru_time = last_accessed lru_model = model_name if lru_model: self.logger.info(f"♻️ 卸载最近最少使用的模型: {lru_model}") self._unload_single_model(lru_model) def _load_image_model(self, model_info: dict, model_path: str) -> Any: """加载图像模型""" self.logger.info(f"🖼️ 加载图像模型: {model_path}") try: # 示例:使用diffusers库加载SDXL模型 from diffusers import StableDiffusionPipeline import torch device = "cuda" if self.use_gpu and torch.cuda.is_available() else "cpu" pipeline = StableDiffusionPipeline.from_pretrained( model_path, cache_dir=self.cache_dir, torch_dtype=torch.float16 if device == "cuda" else torch.float32 ).to(device) return { "pipeline": pipeline, "info": model_info } except ImportError: self.logger.error("❌ diffusers库未安装,无法加载图像模型") raise except Exception as e: self.logger.error(f"❌ 图像模型加载失败: {str(e)}") raise def _load_audio_model(self, model_info: dict, model_path: str) -> Any: """加载音频模型""" self.logger.info(f"🎵 加载音频模型: {model_path}") try: # 示例:加载语音识别模型 from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq import torch device = "cuda" if self.use_gpu and torch.cuda.is_available() else "cpu" processor = AutoProcessor.from_pretrained(model_path, cache_dir=self.cache_dir) model = AutoModelForSpeechSeq2Seq.from_pretrained( model_path, cache_dir=self.cache_dir, torch_dtype=torch.float16 if device == "cuda" else torch.float32 ).to(device) return { "model": model, "processor": processor, "info": model_info } except ImportError: self.logger.error("❌ transformers库未安装,无法加载音频模型") raise except Exception as e: self.logger.error(f"❌ 音频模型加载失败: {str(e)}") raise def shutdown(self): """关闭模型管理器""" self.logger.info("🛑 关闭模型管理器...") self.unload_model() # 卸载所有模型 # 清理缓存(可选) self.logger.info("✅ 模型管理器已关闭") 2.# E:\AI_System\main.py import os import sys import time import logging import signal import json from pathlib import Path # 修复导入问题 - 确保根目录在路径中 sys.path.insert(0, str(Path(__file__).parent.resolve())) try: from core.config_system import CoreConfig except ImportError as e: print(f"❌ 导入错误: {e}") sys.exit(1) try: from core.command_listener import start_command_listener from agent.model_manager import ModelManager from core.cognitive_orchestrator import CognitiveOrchestrator from agent.environment_interface import EnvironmentInterface from agent.diagnostic_system import DiagnosticSystem from utils.path_utils import normalize_path, clean_path_cache except ImportError as e: print(f"❌ 关键模块导入失败: {e}") sys.exit(1) # ====================== 基础路径配置 ====================== # 确保所有操作都在E盘 BASE_DRIVE = "E:" PROJECT_ROOT = Path(__file__).parent.resolve() WORKSPACE_PATH = Path(BASE_DRIVE) / "AI_Workspace" # 创建必要目录 WORKSPACE_PATH.mkdir(parents=True, exist_ok=True) # ====================== 日志配置 ====================== def setup_logging() -> logging.Logger: """配置日志系统(完全在E盘操作)""" logs_dir = WORKSPACE_PATH / "logs" logs_dir.mkdir(parents=True, exist_ok=True) # 配置日志级别 log_level_name = CoreConfig.get("log_level", "INFO").upper() log_level_mapping = { "DEBUG": logging.DEBUG, "INFO": logging.INFO, "WARNING": logging.WARNING, "ERROR": logging.ERROR, "CRITICAL": logging.CRITICAL } log_level = log_level_mapping.get(log_level_name, logging.INFO) # 配置日志格式 log_format = "%(asctime)s - [%(levelname)s] - %(name)s - %(message)s" formatter = logging.Formatter(log_format) # 配置根日志记录器 root_logger = logging.getLogger() root_logger.setLevel(log_level) # 移除所有现有处理器 for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) # 创建日志文件(在E盘) log_file = logs_dir / f"system_{time.strftime('%Y%m%d_%H%M%S')}.log" try: file_handler = logging.FileHandler(log_file, encoding='utf-8') file_handler.setFormatter(formatter) root_logger.addHandler(file_handler) except Exception as e: print(f"⚠️ 无法创建日志文件: {e}") # 添加控制台处理器 console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) root_logger.addHandler(console_handler) # 创建主日志记录器 main_logger = root_logger.getChild("Main") main_logger.info(f"日志系统初始化完成 (级别: {log_level_name})") main_logger.info(f"日志文件位置: {log_file}") return main_logger # ====================== 模型路径处理 ====================== def get_model_paths(model_name: str) -> list: """返回所有可能的模型路径(仅限E盘)""" return [ WORKSPACE_PATH / "AI_Models" / model_name, Path(BASE_DRIVE) / "AI_Models" / model_name, Path(BASE_DRIVE) / "Models" / model_name, WORKSPACE_PATH / "models" / model_name ] def find_valid_model_path(model_name: str, logger: logging.Logger) -> Path: """在E盘查找有效的模型路径""" possible_paths = get_model_paths(model_name) for path in possible_paths: normalized_path = normalize_path(str(path)) if os.path.exists(normalized_path): logger.info(f"✅ 找到模型路径: {normalized_path}") return Path(normalized_path) # 没有找到有效路径 logger.critical(f"🛑 在E盘找不到模型: {model_name}") logger.critical("检查位置:") for path in possible_paths: logger.critical(f" - {path}") raise FileNotFoundError(f"在E盘找不到模型: {model_name}") # ====================== 主函数 ====================== def main(): """主函数(完全E盘操作)""" # 清理路径缓存 clean_path_cache() # 初始化配置系统 CoreConfig.initialize() # 设置日志 logger = setup_logging() # 记录启动信息 logger.info("=" * 50) logger.info(f"🌟 启动AI系统 - 弹性星型架构") logger.info(f"🚀 工作空间: {WORKSPACE_PATH}") logger.info(f"📂 项目目录: {PROJECT_ROOT}") logger.info(f"🐍 Python版本: {sys.version}") logger.info(f"🖥️ 操作系统: {sys.platform}") logger.info("=" * 50) # 初始化模型管理器 try: # 获取模型管理器配置 model_manager_config = CoreConfig.get("model_manager", {}) # 模型缓存目录 cache_dir = WORKSPACE_PATH / "model_cache" cache_dir.mkdir(parents=True, exist_ok=True) model_manager = ModelManager( config=model_manager_config, cache_dir=str(cache_dir), use_gpu=model_manager_config.get("use_gpu", True), max_models_in_memory=model_manager_config.get("max_models_in_memory", 3) ) logger.info("✅ 模型管理器初始化完成") except Exception as e: logger.error(f"❌ 模型管理器初始化失败: {e}", exc_info=True) sys.exit(1) # 初始化卫星模块 - 关键修复 try: # 获取模块配置 ei_config = CoreConfig.get("environment_interface", {}) ds_config = CoreConfig.get("diagnostic_system", {}) # 创建环境接口实例 environment_interface = EnvironmentInterface( name="环境接口", config=ei_config ) # 创建诊断系统实例 diagnostic_system = DiagnosticSystem( name="诊断系统", config=ds_config ) logger.info("✅ 卫星模块初始化完成") except Exception as e: logger.error(f"❌ 卫星模块初始化失败: {e}", exc_info=True) sys.exit(1) # 加载基础模型(仅在E盘查找) base_model_key = "TEXT_BASE" # 直接从配置获取模型设置 base_model_info = CoreConfig.get("model_settings.TEXT_BASE", {}) model_type = base_model_info.get("type", "text") model_name = base_model_info.get("name", "Qwen2-7B") try: # 查找模型路径 model_path = find_valid_model_path(model_name, logger) # 注册并加载模型 if model_manager.register_model(base_model_key, str(model_path), model_type): success, model = model_manager.load_model(base_model_key) if success: logger.info(f"✅ 基础模型加载成功: {base_model_key}") else: logger.error(f"❌ 模型加载失败: {base_model_key}") raise RuntimeError(f"模型加载失败: {base_model_key}") else: logger.error(f"❌ 模型注册失败: {base_model_key}") raise RuntimeError(f"模型注册失败: {base_model_key}") except Exception as e: logger.error(f"❌ 模型初始化失败: {e}", exc_info=True) sys.exit(1) # 初始化星型协调器 try: # 获取认知配置 cognitive_config = CoreConfig.get("cognitive_config", {}) # 系统状态存储目录 state_dir = WORKSPACE_PATH / "system_state" state_dir.mkdir(parents=True, exist_ok=True) cognitive_config["state_dir"] = str(state_dir) # 创建星型协调器 orchestrator = CognitiveOrchestrator(config=cognitive_config) # 连接模块 - 关键步骤 if orchestrator.connect_modules(): logger.info("✅ 星型架构协调器初始化成功") else: logger.warning("⚠️ 模块连接存在问题,系统进入降级模式") except Exception as e: logger.error(f"❌ 协调器初始化失败: {e}", exc_info=True) sys.exit(1) # 命令处理器 def command_handler(command: str) -> dict: """处理用户命令""" logger.info(f"🔄 处理命令: {command}") # 特殊命令处理 if command.lower() in ["exit", "quit", "stop"]: return {"action": "shutdown", "message": "正在关闭系统..."} if command.lower() in ["status", "health"]: return orchestrator.get_system_status() try: # 通过协调器处理命令 return orchestrator.process_command(command) except Exception as e: logger.error(f"❌ 命令处理错误: {e}", exc_info=True) return {"error": f"处理命令时出错: {e}"} # 系统关闭处理 def shutdown_handler(): """系统关闭处理""" logger.info("🛑 关闭系统中...") try: # 保存状态 if 'orchestrator' in locals() and orchestrator is not None: logger.info("💾 保存系统状态...") orchestrator.save_state() # 关闭协调器 if 'orchestrator' in locals() and orchestrator is not None: logger.info("🛑 关闭协调器...") orchestrator.shutdown() # 关闭模型管理器 if 'model_manager' in locals() and model_manager is not None: logger.info("🛑 关闭模型管理器...") model_manager.shutdown() # 关闭卫星模块 if 'environment_interface' in locals() and environment_interface is not None: logger.info("🛑 关闭环境接口...") environment_interface.shutdown() if 'diagnostic_system' in locals() and diagnostic_system is not None: logger.info("🛑 关闭诊断系统...") diagnostic_system.shutdown() logger.info("✅ 系统已完全关闭") sys.exit(0) except Exception as e: logger.error(f"❌ 关闭过程中出错: {e}", exc_info=True) sys.exit(1) # 信号处理 def signal_handler(sig, frame): """处理系统信号""" signals = {signal.SIGINT: "Ctrl+C", signal.SIGTERM: "终止信号"} logger.warning(f"⚠️ 收到 {signals.get(sig, sig)},关闭系统...") shutdown_handler() signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # 启动命令监听器 try: command_listener = start_command_listener(command_handler=command_handler) logger.info("✅ 命令监听器已启动") except Exception as e: logger.error(f"❌ 命令监听器启动失败: {e}", exc_info=True) shutdown_handler() return logger.info("🌟 系统准备就绪! 输入命令开始交互 ('help' 查看命令列表)") # 主循环 try: while True: # 休眠避免CPU占用过高 time.sleep(0.1) # 检查是否有命令需要处理 if command_listener.command_queue.empty(): continue # 处理命令队列 while not command_listener.command_queue.empty(): command = command_listener.command_queue.get() response = command_handler(command) # 处理关闭指令 if isinstance(response, dict) and response.get("action") == "shutdown": shutdown_handler() return # 打印响应 if response: print("\n" + ("-" * 50)) if isinstance(response, dict): response_str = json.dumps(response, indent=2, ensure_ascii=False) print(f"系统响应:\n{response_str}") else: print(f"系统响应: {str(response)}") print("-" * 50 + "\n") except KeyboardInterrupt: logger.info("🛑 用户中断操作,关闭系统...") shutdown_handler() except Exception as e: logger.critical(f"🔥 主循环错误: {e}", exc_info=True) shutdown_handler() # 全局异常处理 def global_exception_handler(exc_type, exc_value, exc_traceback): """全局异常处理器""" if issubclass(exc_type, KeyboardInterrupt): sys.excepthook(exc_type, exc_value, exc_traceback) return # 尝试记录错误,如果日志系统尚未设置,直接打印 try: logging.error("未捕获的全局异常", exc_info=(exc_type, exc_value, exc_traceback)) except: pass print(f"🔥 严重错误: {exc_type.__name__}: {exc_value}") sys.exit(1) if __name__ == "__main__": sys.excepthook = global_exception_handler try: main() except Exception as e: print(f"🔥 主函数异常: {e}") sys.exit(1) 3.E:\AI_System\model_registry.json:”{ "TEXT_BASE": { "path": "E:\\AI_System\\AI_Models\\Qwen2-7B", "type": "text", "status": "unloaded", "checksum": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "last_accessed": 1756551316.8980067, "adapter": null, "is_local": true }, "TEXT_CHAT": { "path": "E:\\AI_System\\local_models/text_chat", "type": "text", "status": "unloaded", "checksum": "unknown", "last_accessed": 1756540612.467894, "adapter": null, "is_local": false }, "IMAGE_MODEL": { "path": "E:\\AI_System\\local_models/image_model", "type": "image", "status": "unloaded", "checksum": "unknown", "last_accessed": 1756540612.4689403, "adapter": null, "is_local": false } }“ 4.# E:\AI_System\utils\path_utils.py import os import re import logging from pathlib import Path # 路径缓存 _path_cache = {} logger = logging.getLogger("PathUtils") def normalize_path(path: str) -> str: """标准化路径:处理环境变量、用户目录和相对路径""" # 尝试从缓存获取 if path in _path_cache: return _path_cache[path] # 处理环境变量 if "$" in path or "%" in path: expanded = os.path.expandvars(path) else: expanded = path # 处理用户目录 if expanded.startswith("~"): expanded = os.path.expanduser(expanded) # 转换为绝对路径 abs_path = os.path.abspath(expanded) # 缓存结果 _path_cache[path] = abs_path return abs_path def clean_path_cache(): """清除路径缓存""" global _path_cache _path_cache = {} logger.info("✅ 路径缓存已清除") def is_valid_hf_id(model_id: str) -> bool: """ 检查是否为有效的Hugging Face模型ID """ if not isinstance(model_id, str): return False # 检查格式:必须包含一个斜杠 if model_id.count('/') != 1: return False # 拆分用户名和模型名 user_part, model_part = model_id.split('/') # 检查用户名部分 if not re.match(r"^[a-zA-Z0-9-_]{3,}$", user_part): return False # 检查模型名部分 if not re.match(r"^[a-zA-Z0-9-_\.]{3,}$", model_part): return False return True def is_local_path(path: str) -> bool: """ 检查路径是否为有效的本地路径 """ try: path = normalize_path(path) return os.path.exists(path) except Exception: return False def find_model_path(model_name: str) -> str: """ 在 E:\AI_Models 目录中查找模型路径 """ # 只在 E:\AI_Models 目录中查找 base_dir = "E:\\AI_Models" # 尝试直接匹配模型目录 model_dir = os.path.join(base_dir, model_name) if os.path.exists(model_dir): return normalize_path(model_dir) # 尝试匹配可能的变体 possible_variants = [ model_name, model_name.replace("-", "_"), model_name.lower(), model_name.replace("_", "-"), ] # 添加常见后缀变体 for variant in list(possible_variants): # 创建副本避免无限循环 if variant.endswith("-base"): possible_variants.append(variant[:-5]) else: possible_variants.append(variant + "-base") possible_variants.append(variant + "-main") # 移除重复项 possible_variants = list(set(possible_variants)) # 尝试每种可能的变体 for variant in possible_variants: variant_path = os.path.join(base_dir, variant) if os.path.exists(variant_path): return normalize_path(variant_path) # 递归搜索子目录 for root, dirs, files in os.walk(base_dir): if model_name in dirs: return normalize_path(os.path.join(root, model_name)) return None def get_all_model_paths() -> dict: """ 获取 E:\AI_Models 目录中的所有模型路径 """ models_dir = "E:\\AI_Models" model_paths = {} if not os.path.exists(models_dir): return model_paths for model_dir in os.listdir(models_dir): full_path = os.path.join(models_dir, model_dir) if os.path.isdir(full_path): model_paths[model_dir] = normalize_path(full_path) return model_paths 5.这些文件我都有 你发的那些不能直接覆盖 对我来说就没有意义 反而会误导我把源文件错误覆盖 导致核心内容丢失 你懂我的意思吗 你要发就发完整的能直接覆盖的 而不是劣质的 虚假的文件
08-31
package com.shop.jieyou.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.shop.jieyou.common.Result; import com.shop.jieyou.entity.UserItem; import com.shop.jieyou.service.PythonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 花卉相关接口控制器 * 提供三大功能: * 1. 获取中国十大名花数据(来自爬虫或缓存) * 2. 手动刷新花卉数据(强制重新爬取) * 3. 基于用户行为的花卉推荐(调用Python协同过滤脚本) */ @RestController @CrossOrigin(origins = "*") // 允许所有域访问,用于前端开发调试(生产环境建议限制域名) @RequestMapping("/api") public class FlowerController { @Autowired private PythonService pythonService; // 注入业务服务层,处理数据获取与推荐逻辑 /** * 接口:GET /api/flowers * 功能:获取“中国十大名花”数据列表 * 数据来源:可能来自数据库、Redis 缓存 或 调用 Python 爬虫脚本 * * @return Result<List<Map<String, Object>>> 返回包含花卉信息的成功响应 */ @GetMapping("/flowers") public Result<List<Map<String, Object>>> getTopTenFlowers() { try { // 调用服务层获取花卉数据(内部可能带缓存机制) List<Map<String, Object>> flowers = pythonService.getFlowers(); return Result.success(flowers); // 成功返回数据 } catch (Exception e) { // 捕获异常并统一返回错误码和消息,避免暴露堆栈给前端 return Result.error("500", "获取花卉数据失败:" + e.getMessage()); } } /** * 接口:POST /api/flowers/refresh * 功能:强制刷新花卉数据缓存,触发重新爬取 * 使用场景:管理员手动更新数据时调用 * * @return Result<Map<String, Object>> 返回刷新结果信息 */ @PostMapping("/flowers/refresh") public Result<Map<String, Object>> refreshData() { try { // TODO: 如果实现了 clearCache 方法,请取消注释并调用 // pythonService.clearCache(); // 清除旧缓存,下次 getFlowers 将重新爬取 // 重新获取最新数据(假设此时会触发爬虫) List<Map<String, Object>> flowers = pythonService.getFlowers(); // 构造返回信息 Map<String, Object> data = new HashMap<>(); data.put("message", "数据已刷新"); data.put("count", flowers.size()); return Result.success(data); } catch (Exception e) { return Result.error("500", "刷新失败:" + e.getMessage()); } } // ========== 推荐系统相关常量定义 ========== /** * 输入文件路径:Java 将用户-商品行为数据写入此 JSON 文件供 Python 脚本读取 * 注意:src/main/resources 是编译后打包进 jar 的资源目录,不适合运行时写入! * 建议改为外部路径如 "./data/input.json" */ private static final String INPUT_PATH = "src/main/resources/scripts/input.json"; /** * 输出文件路径:Python 脚本将推荐结果写入此文件,Java 再读取返回给前端 */ private static final String OUTPUT_PATH = "src/main/resources/scripts/output.json"; /** * Python 协同过滤脚本路径 * 注意:resources 目录下的 .py 文件在打包后无法直接作为可执行脚本运行 * 更佳做法是将脚本放在项目外部或使用 ProcessBuilder 启动独立服务 */ private static final String PYTHON_SCRIPT = "src/main/resources/scripts/collaborative.py"; /** * 接口:GET /api/recommend?userId=123 * 功能:为指定用户生成个性化花卉推荐列表 * 实现方式:Java 查询数据库 → 写入 JSON 文件 → 调用 Python 脚本计算 → 读取结果返回 * * @param userId 用户ID,必填参数 * @return Result<JsonNode> 推荐的商品ID数组(如 [101, 105, 108]) */ @GetMapping("/recommend") public Result recommendFlowers(@RequestParam("userId") Long userId) { try { // 1. 从数据库查询用户-商品交互记录(例如购买、浏览次数) List<UserItem> matrix = pythonService.getUserItemMatrix(); // 2. 使用 Jackson 将数据序列化为 JSON 并写入文件 ObjectMapper mapper = new ObjectMapper(); File inputFile = new File(INPUT_PATH); // ✅ 增强健壮性:确保父目录存在 if (!inputFile.getParentFile().exists()) { inputFile.getParentFile().mkdirs(); // 自动创建 scripts 目录 } mapper.writeValue(inputFile, matrix); // 3. 调用 Python 协同过滤脚本 ProcessBuilder pb = new ProcessBuilder("python", PYTHON_SCRIPT, String.valueOf(userId)); pb.redirectErrorStream(true); // 合并标准输出和错误流,便于捕获日志 Process process = pb.start(); // 等待脚本执行完成 int exitCode = process.waitFor(); if (exitCode != 0) { // Python 脚本执行失败 return Result.error("error", "Python script failed with exit code: " + exitCode); } // 4. 读取 Python 输出的推荐结果 File outputFile = new File(OUTPUT_PATH); if (!outputFile.exists()) { return Result.error("error", "Python script did not generate output file."); } JsonNode result = mapper.readTree(outputFile); // 成功返回推荐结果 return Result.success(result); } catch (Exception e) { // 统一异常处理,返回标准化错误格式 e.printStackTrace(); // 开发期打印堆栈,生产环境应使用日志框架 return Result.error("500", "推荐生成失败:" + e.getMessage()); } } } # -*- coding: utf-8 -*- """ 协同过滤推荐系统脚本(基于物品的协同过滤) 功能:读取用户-商品行为数据,计算商品相似度,为指定用户生成推荐列表 输入:通过命令行传入用户ID,从JSON文件读取用户购买行为数据 输出:将推荐的商品ID列表写入输出JSON文件 """ import sys # 用于获取命令行参数 import json # 用于读写 JSON 格式的数据文件 import pandas as pd # 数据处理库,用于构建矩阵和分析 from sklearn.metrics.pairwise import cosine_similarity # 计算余弦相似度 def main(): """ 主函数:执行完整的推荐流程 步骤: 1. 获取目标用户ID 2. 读取用户-商品交互数据 3. 构建用户-商品评分矩阵 4. 计算商品之间的相似度(Item-Based) 5. 为目标用户生成推荐商品列表 6. 将结果写入输出文件 """ # ========== 第一步:获取命令行传入的用户ID ========== if len(sys.argv) < 2: print("错误:未提供用户ID") print("用法示例:python collaborative_filtering.py <user_id>") sys.exit(1) # 参数不足则退出程序 try: target_user_id = int(sys.argv[1]) # 获取第一个命令行参数作为用户ID except ValueError: print("错误:用户ID必须是一个整数") sys.exit(1) # ========== 第二步:从JSON文件加载用户-商品行为数据 ========== input_file_path = 'src/main/resources/scripts/input.json' # 输入文件路径(由Spring Boot生成) try: with open(input_file_path, 'r', encoding='utf-8') as f: data = json.load(f) # 加载JSON数据为Python列表 except FileNotFoundError: print(f"错误:找不到输入文件 {input_file_path}") sys.exit(1) except json.JSONDecodeError as e: print(f"错误:JSON解析失败:{e}") sys.exit(1) # 将数据转换为 Pandas DataFrame df = pd.DataFrame(data) # 如果没有数据,直接返回空推荐 if df.empty: print("警告:输入数据为空,无法进行推荐") with open('src/main/resources/scripts/output.json', 'w', encoding='utf-8') as f: json.dump([], f) # 输出空列表 return # ========== 第三步:构建用户-商品交互矩阵 ========== # 行:用户(userId),列:商品(productId),值:购买次数(count) # fillna(0):将缺失值(未购买)补为0 user_item_matrix = df.pivot(index='userId', columns='productId', values='count').fillna(0) print(f"用户-商品矩阵形状:{user_item_matrix.shape}") # ========== 第四步:计算商品之间的相似度(Item-Based 协同过滤)========== # 使用余弦相似度衡量两个商品被相同用户购买的模式是否相似 # .T 表示转置:把商品变为行,用户变为列,便于计算商品间相似性 item_similarity = cosine_similarity(user_item_matrix.T) # 得到相似度矩阵(二维数组) # 将相似度数组转为DataFrame,方便按商品ID索引 item_sim_df = pd.DataFrame( item_similarity, index=user_item_matrix.columns, # 商品ID作为行索引 columns=user_item_matrix.columns # 商品ID作为列索引 ) # ========== 第五步:为指定用户生成推荐列表 ========== if target_user_id not in user_item_matrix.index: # 用户不存在于训练数据中(新用户),无法推荐 print(f"警告:用户 {target_user_id} 无历史行为数据,返回空推荐(冷启动)") recommendations = [] else: # 获取该用户的购买记录 user_ratings = user_item_matrix.loc[target_user_id] # Series: productId -> count # 找出该用户已购买的商品 bought_items = user_ratings[user_ratings > 0].index # 已购商品ID列表 # 初始化一个Series用于累计每个商品的推荐得分 scores = pd.Series(0, index=item_sim_df.index) # 初始全为0 # 对每一个用户买过的商品,将其相似商品的得分加权累加 for item in bought_items: # item_sim_df[item]: 当前商品与其他所有商品的相似度 # user_ratings[item]: 用户对该商品的“兴趣强度”(可用购买次数表示) scores += item_sim_df[item] * user_ratings[item] # 过滤掉用户已经买过的商品 candidate_scores = scores.drop(bought_items, errors='ignore') # 按得分降序排列,取前5个最相似且未购买的商品 top_recommendations = candidate_scores.sort_values(ascending=False).head(5) # 提取商品ID并转为整数列表 rec_list = top_recommendations.index.tolist() recommendations = [int(pid) for pid in rec_list] print(f"为用户 {target_user_id} 推荐商品: {recommendations}") # ========== 第六步:将推荐结果写入输出文件 ========== output_file_path = 'resources/temp/output.json' try: with open(output_file_path, 'w', encoding='utf-8') as f: json.dump(recommendations, f, ensure_ascii=False, indent=2) # 格式化保存 print("推荐结果已成功写入输出文件") except Exception as e: print(f"错误:无法写入输出文件 {output_file_path}: {e}") sys.exit(1) # 程序入口点 if __name__ == "__main__": """ 当脚本被直接运行时(而非被导入),执行 main() 函数 示例运行命令: python collaborative_filtering.py 123 """ main() # -*- coding: utf-8 -*- """ 协同过滤推荐系统脚本(基于物品的协同过滤) 功能:读取用户-商品行为数据,计算商品相似度,为指定用户生成推荐列表 输入:通过命令行传入用户ID,从JSON文件读取用户购买行为数据 输出:将推荐的商品ID列表写入输出JSON文件 """ import sys # 用于获取命令行参数 import json # 用于读写 JSON 格式的数据文件 import pandas as pd # 数据处理库,用于构建矩阵和分析 from sklearn.metrics.pairwise import cosine_similarity # 计算余弦相似度 def main(): """ 主函数:执行完整的推荐流程 步骤: 1. 获取目标用户ID 2. 读取用户-商品交互数据 3. 构建用户-商品评分矩阵 4. 计算商品之间的相似度(Item-Based) 5. 为目标用户生成推荐商品列表 6. 将结果写入输出文件 """ # ========== 第一步:获取命令行传入的用户ID ========== if len(sys.argv) < 2: print("错误:未提供用户ID") print("用法示例:python collaborative_filtering.py <user_id>") sys.exit(1) # 参数不足则退出程序 try: target_user_id = int(sys.argv[1]) # 获取第一个命令行参数作为用户ID except ValueError: print("错误:用户ID必须是一个整数") sys.exit(1) # ========== 第二步:从JSON文件加载用户-商品行为数据 ========== input_file_path = 'src/main/resources/scripts/input.json' # 输入文件路径(由Spring Boot生成) try: with open(input_file_path, 'r', encoding='utf-8') as f: data = json.load(f) # 加载JSON数据为Python列表 except FileNotFoundError: print(f"错误:找不到输入文件 {input_file_path}") sys.exit(1) except json.JSONDecodeError as e: print(f"错误:JSON解析失败:{e}") sys.exit(1) # 将数据转换为 Pandas DataFrame df = pd.DataFrame(data) # 如果没有数据,直接返回空推荐 if df.empty: print("警告:输入数据为空,无法进行推荐") with open('src/main/resources/scripts/output.json', 'w', encoding='utf-8') as f: json.dump([], f) # 输出空列表 return # ========== 第三步:构建用户-商品交互矩阵 ========== # 行:用户(userId),列:商品(productId),值:购买次数(count) # fillna(0):将缺失值(未购买)补为0 user_item_matrix = df.pivot(index='userId', columns='productId', values='count').fillna(0) print(f"用户-商品矩阵形状:{user_item_matrix.shape}") # ========== 第四步:计算商品之间的相似度(Item-Based 协同过滤)========== # 使用余弦相似度衡量两个商品被相同用户购买的模式是否相似 # .T 表示转置:把商品变为行,用户变为列,便于计算商品间相似性 item_similarity = cosine_similarity(user_item_matrix.T) # 得到相似度矩阵(二维数组) # 将相似度数组转为DataFrame,方便按商品ID索引 item_sim_df = pd.DataFrame( item_similarity, index=user_item_matrix.columns, # 商品ID作为行索引 columns=user_item_matrix.columns # 商品ID作为列索引 ) # ========== 第五步:为指定用户生成推荐列表 ========== if target_user_id not in user_item_matrix.index: # 用户不存在于训练数据中(新用户),无法推荐 print(f"警告:用户 {target_user_id} 无历史行为数据,返回空推荐(冷启动)") recommendations = [] else: # 获取该用户的购买记录 user_ratings = user_item_matrix.loc[target_user_id] # Series: productId -> count # 找出该用户已购买的商品 bought_items = user_ratings[user_ratings > 0].index # 已购商品ID列表 # 初始化一个Series用于累计每个商品的推荐得分 scores = pd.Series(0, index=item_sim_df.index) # 初始全为0 # 对每一个用户买过的商品,将其相似商品的得分加权累加 for item in bought_items: # item_sim_df[item]: 当前商品与其他所有商品的相似度 # user_ratings[item]: 用户对该商品的“兴趣强度”(可用购买次数表示) scores += item_sim_df[item] * user_ratings[item] # 过滤掉用户已经买过的商品 candidate_scores = scores.drop(bought_items, errors='ignore') # 按得分降序排列,取前5个最相似且未购买的商品 top_recommendations = candidate_scores.sort_values(ascending=False).head(5) # 提取商品ID并转为整数列表 rec_list = top_recommendations.index.tolist() recommendations = [int(pid) for pid in rec_list] print(f"为用户 {target_user_id} 推荐商品: {recommendations}") # ========== 第六步:将推荐结果写入输出文件 ========== output_file_path = 'resources/temp/output.json' try: with open(output_file_path, 'w', encoding='utf-8') as f: json.dump(recommendations, f, ensure_ascii=False, indent=2) # 格式化保存 print("推荐结果已成功写入输出文件") except Exception as e: print(f"错误:无法写入输出文件 {output_file_path}: {e}") sys.exit(1) # 程序入口点 if __name__ == "__main__": """ 当脚本被直接运行时(而非被导入),执行 main() 函数 示例运行命令: python collaborative_filtering.py 123 """ main()
10-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值