diff --git a/.gitignore b/.gitignore index b9e101e40..34c7b1e28 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ log/ logs/ /test /src/test +nonebot-maibot-adapter/ +*.zip +run.bat +run.py message_queue_content.txt message_queue_content.bat message_queue_window.bat diff --git a/src/plugins/config/config.py b/src/plugins/config/config.py index f4969df6b..36dc67c3a 100644 --- a/src/plugins/config/config.py +++ b/src/plugins/config/config.py @@ -1,6 +1,7 @@ import os from dataclasses import dataclass, field from typing import Dict, List, Optional +from dateutil import tz import tomli import tomlkit @@ -151,6 +152,7 @@ class BotConfig: PROMPT_SCHEDULE_GEN = "无日程" SCHEDULE_DOING_UPDATE_INTERVAL: int = 300 # 日程表更新间隔 单位秒 SCHEDULE_TEMPERATURE: float = 0.5 # 日程表温度,建议0.5-1.0 + TIME_ZONE: str = "Asia/Shanghai" # 时区 # message MAX_CONTEXT_SIZE: int = 15 # 上下文最大消息数 @@ -355,6 +357,11 @@ class BotConfig: ) if config.INNER_VERSION in SpecifierSet(">=1.0.2"): config.SCHEDULE_TEMPERATURE = schedule_config.get("schedule_temperature", config.SCHEDULE_TEMPERATURE) + time_zone = schedule_config.get("time_zone", config.TIME_ZONE) + if tz.gettz(time_zone) is None: + logger.error(f"无效的时区: {time_zone},使用默认值: {config.TIME_ZONE}") + else: + config.TIME_ZONE = time_zone def emoji(parent: dict): emoji_config = parent["emoji"] diff --git a/src/plugins/memory_system/Hippocampus.py b/src/plugins/memory_system/Hippocampus.py index 717cebe17..7f781ac31 100644 --- a/src/plugins/memory_system/Hippocampus.py +++ b/src/plugins/memory_system/Hippocampus.py @@ -14,7 +14,6 @@ from src.common.logger import get_module_logger, LogConfig, MEMORY_STYLE_CONFIG from src.plugins.memory_system.sample_distribution import MemoryBuildScheduler # 分布生成器 from .memory_config import MemoryConfig - def get_closest_chat_from_db(length: int, timestamp: str): # print(f"获取最接近指定时间戳的聊天记录,长度: {length}, 时间戳: {timestamp}") # print(f"当前时间: {timestamp},转换后时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp))}") diff --git a/src/plugins/models/utils_model.py b/src/plugins/models/utils_model.py index 260c5f5a6..f29f1fa47 100644 --- a/src/plugins/models/utils_model.py +++ b/src/plugins/models/utils_model.py @@ -153,7 +153,7 @@ class LLM_request: # 合并重试策略 default_retry = { - "max_retries": 3, + "max_retries": 10, "base_wait": 15, "retry_codes": [429, 413, 500, 503], "abort_codes": [400, 401, 402, 403], @@ -179,9 +179,6 @@ class LLM_request: # logger.debug(f"{logger_msg}发送请求到URL: {api_url}") # logger.info(f"使用模型: {self.model_name}") - # 流式输出标志 - if stream_mode: - payload["stream"] = stream_mode # 构建请求体 if image_base64: @@ -189,6 +186,11 @@ class LLM_request: elif payload is None: payload = await self._build_payload(prompt) + # 流式输出标志 + # 先构建payload,再添加流式输出标志 + if stream_mode: + payload["stream"] = stream_mode + for retry in range(policy["max_retries"]): try: # 使用上下文管理器处理会话 @@ -203,21 +205,21 @@ class LLM_request: # 处理需要重试的状态码 if response.status in policy["retry_codes"]: wait_time = policy["base_wait"] * (2**retry) - logger.warning(f"错误码: {response.status}, 等待 {wait_time}秒后重试") + logger.warning(f"模型 {self.model_name} 错误码: {response.status}, 等待 {wait_time}秒后重试") if response.status == 413: logger.warning("请求体过大,尝试压缩...") image_base64 = compress_base64_image_by_scale(image_base64) payload = await self._build_payload(prompt, image_base64, image_format) elif response.status in [500, 503]: - logger.error(f"错误码: {response.status} - {error_code_mapping.get(response.status)}") + logger.error(f"模型 {self.model_name} 错误码: {response.status} - {error_code_mapping.get(response.status)}") raise RuntimeError("服务器负载过高,模型恢复失败QAQ") else: - logger.warning(f"请求限制(429),等待{wait_time}秒后重试...") + logger.warning(f"模型 {self.model_name} 请求限制(429),等待{wait_time}秒后重试...") await asyncio.sleep(wait_time) continue elif response.status in policy["abort_codes"]: - logger.error(f"错误码: {response.status} - {error_code_mapping.get(response.status)}") + logger.error(f"模型 {self.model_name} 错误码: {response.status} - {error_code_mapping.get(response.status)}") # 尝试获取并记录服务器返回的详细错误信息 try: error_json = await response.json() @@ -319,9 +321,9 @@ class LLM_request: flag_delta_content_finished = True except Exception as e: - logger.exception(f"解析流式输出错误: {str(e)}") + logger.exception(f"模型 {self.model_name} 解析流式输出错误: {str(e)}") except GeneratorExit: - logger.warning("流式输出被中断,正在清理资源...") + logger.warning("模型 {self.model_name} 流式输出被中断,正在清理资源...") # 确保资源被正确清理 await response.release() # 返回已经累积的内容 @@ -335,7 +337,7 @@ class LLM_request: else self._default_response_handler(result, user_id, request_type, endpoint) ) except Exception as e: - logger.error(f"处理流式输出时发生错误: {str(e)}") + logger.error(f"模型 {self.model_name} 处理流式输出时发生错误: {str(e)}") # 确保在发生错误时也能正确清理资源 try: await response.release() @@ -378,21 +380,21 @@ class LLM_request: except (aiohttp.ClientError, asyncio.TimeoutError) as e: if retry < policy["max_retries"] - 1: wait_time = policy["base_wait"] * (2**retry) - logger.error(f"网络错误,等待{wait_time}秒后重试... 错误: {str(e)}") + logger.error(f"模型 {self.model_name} 网络错误,等待{wait_time}秒后重试... 错误: {str(e)}") await asyncio.sleep(wait_time) continue else: - logger.critical(f"网络错误达到最大重试次数: {str(e)}") + logger.critical(f"模型 {self.model_name} 网络错误达到最大重试次数: {str(e)}") raise RuntimeError(f"网络请求失败: {str(e)}") from e except Exception as e: - logger.critical(f"未预期的错误: {str(e)}") + logger.critical(f"模型 {self.model_name} 未预期的错误: {str(e)}") raise RuntimeError(f"请求过程中发生错误: {str(e)}") from e except aiohttp.ClientResponseError as e: # 处理aiohttp抛出的响应错误 if retry < policy["max_retries"] - 1: wait_time = policy["base_wait"] * (2**retry) - logger.error(f"HTTP响应错误,等待{wait_time}秒后重试... 状态码: {e.status}, 错误: {e.message}") + logger.error(f"模型 {self.model_name} HTTP响应错误,等待{wait_time}秒后重试... 状态码: {e.status}, 错误: {e.message}") try: if hasattr(e, "response") and e.response and hasattr(e.response, "text"): error_text = await e.response.text() @@ -403,27 +405,27 @@ class LLM_request: if "error" in error_item and isinstance(error_item["error"], dict): error_obj = error_item["error"] logger.error( - f"服务器错误详情: 代码={error_obj.get('code')}, " + f"模型 {self.model_name} 服务器错误详情: 代码={error_obj.get('code')}, " f"状态={error_obj.get('status')}, " f"消息={error_obj.get('message')}" ) elif isinstance(error_json, dict) and "error" in error_json: error_obj = error_json.get("error", {}) logger.error( - f"服务器错误详情: 代码={error_obj.get('code')}, " + f"模型 {self.model_name} 服务器错误详情: 代码={error_obj.get('code')}, " f"状态={error_obj.get('status')}, " f"消息={error_obj.get('message')}" ) else: - logger.error(f"服务器错误响应: {error_json}") + logger.error(f"模型 {self.model_name} 服务器错误响应: {error_json}") except (json.JSONDecodeError, TypeError) as json_err: - logger.warning(f"响应不是有效的JSON: {str(json_err)}, 原始内容: {error_text[:200]}") + logger.warning(f"模型 {self.model_name} 响应不是有效的JSON: {str(json_err)}, 原始内容: {error_text[:200]}") except (AttributeError, TypeError, ValueError) as parse_err: - logger.warning(f"无法解析响应错误内容: {str(parse_err)}") + logger.warning(f"模型 {self.model_name} 无法解析响应错误内容: {str(parse_err)}") await asyncio.sleep(wait_time) else: - logger.critical(f"HTTP响应错误达到最大重试次数: 状态码: {e.status}, 错误: {e.message}") + logger.critical(f"模型 {self.model_name} HTTP响应错误达到最大重试次数: 状态码: {e.status}, 错误: {e.message}") # 安全地检查和记录请求详情 if ( image_base64 @@ -440,14 +442,14 @@ class LLM_request: f"{image_base64[:10]}...{image_base64[-10:]}" ) logger.critical(f"请求头: {await self._build_headers(no_key=True)} 请求体: {payload}") - raise RuntimeError(f"API请求失败: 状态码 {e.status}, {e.message}") from e + raise RuntimeError(f"模型 {self.model_name} API请求失败: 状态码 {e.status}, {e.message}") from e except Exception as e: if retry < policy["max_retries"] - 1: wait_time = policy["base_wait"] * (2**retry) - logger.error(f"请求失败,等待{wait_time}秒后重试... 错误: {str(e)}") + logger.error(f"模型 {self.model_name} 请求失败,等待{wait_time}秒后重试... 错误: {str(e)}") await asyncio.sleep(wait_time) else: - logger.critical(f"请求失败: {str(e)}") + logger.critical(f"模型 {self.model_name} 请求失败: {str(e)}") # 安全地检查和记录请求详情 if ( image_base64 @@ -464,10 +466,10 @@ class LLM_request: f"{image_base64[:10]}...{image_base64[-10:]}" ) logger.critical(f"请求头: {await self._build_headers(no_key=True)} 请求体: {payload}") - raise RuntimeError(f"API请求失败: {str(e)}") from e + raise RuntimeError(f"模型 {self.model_name} API请求失败: {str(e)}") from e - logger.error("达到最大重试次数,请求仍然失败") - raise RuntimeError("达到最大重试次数,API请求仍然失败") + logger.error(f"模型 {self.model_name} 达到最大重试次数,请求仍然失败,错误: {str(e)}") + raise RuntimeError(f"模型 {self.model_name} 达到最大重试次数,API请求仍然失败,错误: {str(e)}") async def _transform_parameters(self, params: dict) -> dict: """ diff --git a/src/plugins/schedule/schedule_generator.py b/src/plugins/schedule/schedule_generator.py index ecc032761..edce54b64 100644 --- a/src/plugins/schedule/schedule_generator.py +++ b/src/plugins/schedule/schedule_generator.py @@ -3,6 +3,7 @@ import os import sys from typing import Dict import asyncio +from dateutil import tz # 添加项目根目录到 Python 路径 root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) @@ -13,6 +14,8 @@ from src.common.logger import get_module_logger, SCHEDULE_STYLE_CONFIG, LogConfi from src.plugins.models.utils_model import LLM_request # noqa: E402 from src.plugins.config.config import global_config # noqa: E402 +TIME_ZONE = tz.gettz(global_config.TIME_ZONE) # 设置时区 + schedule_config = LogConfig( # 使用海马体专用样式 @@ -44,7 +47,7 @@ class ScheduleGenerator: self.personality = "" self.behavior = "" - self.start_time = datetime.datetime.now() + self.start_time = datetime.datetime.now(TIME_ZONE) self.schedule_doing_update_interval = 300 # 最好大于60 @@ -74,7 +77,7 @@ class ScheduleGenerator: while True: # print(self.get_current_num_task(1, True)) - current_time = datetime.datetime.now() + current_time = datetime.datetime.now(TIME_ZONE) # 检查是否需要重新生成日程(日期变化) if current_time.date() != self.start_time.date(): @@ -100,7 +103,7 @@ class ScheduleGenerator: Returns: tuple: (today_schedule_text, today_schedule) 今天的日程文本和解析后的日程字典 """ - today = datetime.datetime.now() + today = datetime.datetime.now(TIME_ZONE) yesterday = today - datetime.timedelta(days=1) # 先检查昨天的日程 @@ -156,7 +159,7 @@ class ScheduleGenerator: """打印完整的日程安排""" if not self.today_schedule_text: logger.warning("今日日程有误,将在下次运行时重新生成") - db.schedule.delete_one({"date": datetime.datetime.now().strftime("%Y-%m-%d")}) + db.schedule.delete_one({"date": datetime.datetime.now(TIME_ZONE).strftime("%Y-%m-%d")}) else: logger.info("=== 今日日程安排 ===") logger.info(self.today_schedule_text) @@ -165,7 +168,7 @@ class ScheduleGenerator: async def update_today_done_list(self): # 更新数据库中的 today_done_list - today_str = datetime.datetime.now().strftime("%Y-%m-%d") + today_str = datetime.datetime.now(TIME_ZONE).strftime("%Y-%m-%d") existing_schedule = db.schedule.find_one({"date": today_str}) if existing_schedule: @@ -177,7 +180,7 @@ class ScheduleGenerator: async def move_doing(self, mind_thinking: str = ""): try: - current_time = datetime.datetime.now() + current_time = datetime.datetime.now(TIME_ZONE) if mind_thinking: doing_prompt = self.construct_doing_prompt(current_time, mind_thinking) else: @@ -246,7 +249,7 @@ class ScheduleGenerator: def save_today_schedule_to_db(self): """保存日程到数据库,同时初始化 today_done_list""" - date_str = datetime.datetime.now().strftime("%Y-%m-%d") + date_str = datetime.datetime.now(TIME_ZONE).strftime("%Y-%m-%d") schedule_data = { "date": date_str, "schedule": self.today_schedule_text, diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index a913777cf..a313f5760 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -48,6 +48,7 @@ enable_schedule_gen = true # 是否启用日程表(尚未完成) prompt_schedule_gen = "用几句话描述描述性格特点或行动规律,这个特征会用来生成日程表" schedule_doing_update_interval = 900 # 日程表更新间隔 单位秒 schedule_temperature = 0.3 # 日程表温度,建议0.3-0.6 +time_zone = "Asia/Shanghai" # 给你的机器人设置时区,可以解决运行电脑时区和国内时区不同的情况,或者模拟国外留学生日程 [platforms] # 必填项目,填写每个平台适配器提供的链接 nonebot-qq="http://127.0.0.1:18002/api/message"