From 2318dbadefc321929fd6b8df8bccc7d6ede22044 Mon Sep 17 00:00:00 2001 From: Cookie987 Date: Tue, 14 Oct 2025 22:52:11 +0800 Subject: [PATCH 1/3] Fix #1288 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 提升统计任务容错性,安全加载 local_storage 数据 --- src/chat/utils/statistic.py | 47 ++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/chat/utils/statistic.py b/src/chat/utils/statistic.py index 2c4dd619..ca064044 100644 --- a/src/chat/utils/statistic.py +++ b/src/chat/utils/statistic.py @@ -519,29 +519,32 @@ class StatisticOutputTask(AsyncTask): last_all_time_stat = None - if "last_full_statistics" in local_storage: - # 如果存在上次完整统计数据,则使用该数据进行增量统计 - last_stat: Dict[str, Any] = local_storage["last_full_statistics"] # 上次完整统计数据 # type: ignore + try: + if "last_full_statistics" in local_storage: + # 如果存在上次完整统计数据,则使用该数据进行增量统计 + last_stat: Dict[str, Any] = local_storage["last_full_statistics"] # 上次完整统计数据 # type: ignore - # 修复 name_mapping 数据类型不匹配问题 - # JSON 中存储为列表,但代码期望为元组 - raw_name_mapping = last_stat["name_mapping"] - self.name_mapping = {} - for chat_id, value in raw_name_mapping.items(): - if isinstance(value, list) and len(value) == 2: - # 将列表转换为元组 - self.name_mapping[chat_id] = (value[0], value[1]) - elif isinstance(value, tuple) and len(value) == 2: - # 已经是元组,直接使用 - self.name_mapping[chat_id] = value - else: - # 数据格式不正确,跳过或使用默认值 - logger.warning(f"name_mapping 中 chat_id {chat_id} 的数据格式不正确: {value}") - continue - last_all_time_stat = last_stat["stat_data"] # 上次完整统计的统计数据 - last_stat_timestamp = datetime.fromtimestamp(last_stat["timestamp"]) # 上次完整统计数据的时间戳 - self.stat_period = [item for item in self.stat_period if item[0] != "all_time"] # 删除"所有时间"的统计时段 - self.stat_period.append(("all_time", now - last_stat_timestamp, "自部署以来的")) + # 修复 name_mapping 数据类型不匹配问题 + # JSON 中存储为列表,但代码期望为元组 + raw_name_mapping = last_stat["name_mapping"] + self.name_mapping = {} + for chat_id, value in raw_name_mapping.items(): + if isinstance(value, list) and len(value) == 2: + # 将列表转换为元组 + self.name_mapping[chat_id] = (value[0], value[1]) + elif isinstance(value, tuple) and len(value) == 2: + # 已经是元组,直接使用 + self.name_mapping[chat_id] = value + else: + # 数据格式不正确,跳过或使用默认值 + logger.warning(f"name_mapping 中 chat_id {chat_id} 的数据格式不正确: {value}") + continue + last_all_time_stat = last_stat["stat_data"] # 上次完整统计的统计数据 + last_stat_timestamp = datetime.fromtimestamp(last_stat["timestamp"]) # 上次完整统计数据的时间戳 + self.stat_period = [item for item in self.stat_period if item[0] != "all_time"] # 删除"所有时间"的统计时段 + self.stat_period.append(("all_time", now - last_stat_timestamp, "自部署以来的")) + except Exception as e: + logger.warning(f"加载上次完整统计数据失败,进行全量统计,错误信息:{e}") stat_start_timestamp = [(period[0], now - period[1]) for period in self.stat_period] From 2f84cb630536d61c8c6fedb91f3898510d137e7c Mon Sep 17 00:00:00 2001 From: magisk317 Date: Tue, 21 Oct 2025 23:34:08 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E5=8E=8B=E7=BC=A9=E5=A4=84=E7=90=86=20embedded=20null=20byte?= =?UTF-8?q?=20=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/llm_models/utils.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/llm_models/utils.py b/src/llm_models/utils.py index 5c760252..57492dce 100644 --- a/src/llm_models/utils.py +++ b/src/llm_models/utils.py @@ -29,12 +29,19 @@ def compress_messages(messages: list[Message], img_target_size: int = 1 * 1024 * :return: 转换后的图片数据 """ try: - image = Image.open(image_data) + image = Image.open(io.BytesIO(image_data)) - if image.format and (image.format.upper() in ["JPEG", "JPG", "PNG", "WEBP"]): - # 静态图像,转换为JPEG格式 + # 仅在非动图时进行格式转换 + if ( + not getattr(image, "is_animated", False) + and image.format + and (image.format.upper() in ["JPEG", "JPG", "PNG", "WEBP"]) + ): reformated_image_data = io.BytesIO() - image.save(reformated_image_data, format="JPEG", quality=95, optimize=True) + img_to_save = image + if img_to_save.mode in ("RGBA", "LA", "P"): + img_to_save = img_to_save.convert("RGB") + img_to_save.save(reformated_image_data, format="JPEG", quality=95, optimize=True) image_data = reformated_image_data.getvalue() return image_data @@ -50,20 +57,22 @@ def compress_messages(messages: list[Message], img_target_size: int = 1 * 1024 * :return: 缩放后的图片数据 """ try: - image = Image.open(image_data) + image = Image.open(io.BytesIO(image_data)) # 原始尺寸 original_size = (image.width, image.height) - # 计算新的尺寸 - new_size = (int(original_size[0] * scale), int(original_size[1] * scale)) + # 计算新的尺寸,防止为0 + new_w = max(1, int(original_size[0] * scale)) + new_h = max(1, int(original_size[1] * scale)) + new_size = (new_w, new_h) output_buffer = io.BytesIO() if getattr(image, "is_animated", False): # 动态图片,处理所有帧 frames = [] - new_size = (new_size[0] // 2, new_size[1] // 2) # 动图,缩放尺寸再打折 + new_size = (max(1, new_size[0] // 2), max(1, new_size[1] // 2)) # 动图,缩放尺寸再打折 for frame_idx in range(getattr(image, "n_frames", 1)): image.seek(frame_idx) new_frame = image.copy() @@ -83,6 +92,8 @@ def compress_messages(messages: list[Message], img_target_size: int = 1 * 1024 * else: # 静态图片,直接缩放保存 resized_image = image.resize(new_size, Image.Resampling.LANCZOS) + if resized_image.mode in ("RGBA", "LA", "P"): + resized_image = resized_image.convert("RGB") resized_image.save(output_buffer, format="JPEG", quality=95, optimize=True) return output_buffer.getvalue(), original_size, new_size From 7a8d01f8e02968abb09a858d7026d16281519590 Mon Sep 17 00:00:00 2001 From: madoka315 <72589546+madoka315@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:29:35 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E4=B8=BA=E5=86=85=E7=BD=AE?= =?UTF-8?q?=E9=A2=91=E7=8E=87=E8=B0=83=E6=95=B4=E6=B7=BB=E5=8A=A0=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D=E5=92=8C=E7=BA=A6=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此commit用于:解决实践中部分模型经常会输出一大段内容,其中包含“过于频繁”关键字,从而导致内置频率调整器误调整的问题。 --- .../frequency_control/frequency_control.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/chat/frequency_control/frequency_control.py b/src/chat/frequency_control/frequency_control.py index 0997ec0c..dfa5a616 100644 --- a/src/chat/frequency_control/frequency_control.py +++ b/src/chat/frequency_control/frequency_control.py @@ -21,6 +21,10 @@ def init_prompt(): 如果用户觉得你的发言过于频繁,请输出"过于频繁",否则输出"正常" 如果用户觉得你的发言过少,请输出"过少",否则输出"正常" +**你只能输出以下三个词之一,不要输出任何其他文字、解释或标点:** +- 正常 +- 过于频繁 +- 过少 """, "frequency_adjust_prompt", ) @@ -103,13 +107,18 @@ class FrequencyControl: logger.info(f"频率调整 reasoning_content: {reasoning_content}") final_value_by_api = frequency_api.get_current_talk_value(self.chat_id) - if "过于频繁" in response: - logger.info(f"频率调整: 过于频繁,调整值到{final_value_by_api}") - self.talk_frequency_adjust = max(0.1, min(3.0, self.talk_frequency_adjust * 0.8)) - elif "过少" in response: - logger.info(f"频率调整: 过少,调整值到{final_value_by_api}") - self.talk_frequency_adjust = max(0.1, min(3.0, self.talk_frequency_adjust * 1.2)) - self.last_frequency_adjust_time = time.time() + + # LLM依然输出过多内容时取消本次调整。合法最多4个字,但有的模型可能会输出一些markdown换行符等,需要长度宽限 + if len(response) < 20: + if "过于频繁" in response: + logger.info(f"频率调整: 过于频繁,调整值到{final_value_by_api}") + self.talk_frequency_adjust = max(0.1, min(3.0, self.talk_frequency_adjust * 0.8)) + elif "过少" in response: + logger.info(f"频率调整: 过少,调整值到{final_value_by_api}") + self.talk_frequency_adjust = max(0.1, min(3.0, self.talk_frequency_adjust * 1.2)) + self.last_frequency_adjust_time = time.time() + else: + logger.info(f"频率调整:response不符合要求,取消本次调整") class FrequencyControlManager: """频率控制管理器,管理多个聊天流的频率控制实例"""