From 87da8e3b5c2ba8948f58c4d41ea270b907af477a Mon Sep 17 00:00:00 2001 From: liuwei Date: Fri, 3 Apr 2026 09:04:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BA=E7=BE=A4=E6=80=BB=E7=BB=93=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=A2=9E=E5=8A=A0=E4=B8=89=E6=AC=A1=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dify 请求失败时不再立即返回失败 - 群总结生成过程最多重试 3 次 - 增加 2 秒、4 秒递增等待,降低偶发错误影响 - 仅在三次都失败后才返回生成总结失败结果 - 补充重试次数与等待时间日志,便于排查总结异常 --- plugins/message_summary/main.py | 116 +++++++++++++++++--------------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/plugins/message_summary/main.py b/plugins/message_summary/main.py index df3384f..c206596 100644 --- a/plugins/message_summary/main.py +++ b/plugins/message_summary/main.py @@ -233,69 +233,75 @@ class MessageSummaryPlugin(MessagePluginInterface): "Content-Type": "application/json" } - try: - custom_timeout = ClientTimeout(total=None, connect=10, sock_read=300) - conn = aiohttp.TCPConnector(keepalive_timeout=60) # 保持连接活跃 - async with aiohttp.ClientSession(connector=conn, timeout=custom_timeout) as session: - async with session.post(self._api_url, headers=headers, json=data) as response: - response.raise_for_status() # 检查请求是否成功 - response_data = await response.json() + max_retries = 3 + retry_delays = [2, 4] - self.LOG.info(f"Dify API响应状态码: {response.status}") - self.LOG.debug(f"响应数据: {json.dumps(response_data, ensure_ascii=False, indent=2)}") + for attempt in range(1, max_retries + 1): + try: + custom_timeout = ClientTimeout(total=None, connect=10, sock_read=300) + conn = aiohttp.TCPConnector(keepalive_timeout=60) # 保持连接活跃 + async with aiohttp.ClientSession(connector=conn, timeout=custom_timeout) as session: + async with session.post(self._api_url, headers=headers, json=data) as response: + response.raise_for_status() # 检查请求是否成功 + response_data = await response.json() - # 提取回答内容 - answer = response_data.get("answer", "") - # 去除广告内容pollinations.ai 的广告 - # answer = remove_trailing_content(answer) - spath = "" - # 提取token使用情况 - metadata = response_data.get("metadata", {}) - usage = metadata.get("usage", {}) + self.LOG.info(f"Dify API响应状态码: {response.status}, attempt={attempt}") + self.LOG.debug(f"响应数据: {json.dumps(response_data, ensure_ascii=False, indent=2)}") - if usage: - prompt_tokens = usage.get("prompt_tokens", 0) - completion_tokens = usage.get("completion_tokens", 0) - total_tokens = usage.get("total_tokens", 0) + # 提取回答内容 + answer = response_data.get("answer", "") + # 去除广告内容pollinations.ai 的广告 + # answer = remove_trailing_content(answer) + spath = "" + # 提取token使用情况 + metadata = response_data.get("metadata", {}) + usage = metadata.get("usage", {}) - # 添加token信息 - tokens_info = f"\n\n【tokens】输入: {prompt_tokens} 生成: {completion_tokens} 总: {total_tokens}" - answer += tokens_info - try: - # 使用唯一文件名并指定完整路径 - timestamp = int(time.time()) - output_path = f"summary_{timestamp}.png" - # 构建完整的输出路径 - self.LOG.info(f"开始生成图片: {output_path}") - spath = await convert_md_str_to_image(answer, output_path) - self.LOG.info(f"成功生成图片: {spath}") - except Exception as e: - self.LOG.error(f"生成图片失败: {e}", exc_info=True) - # 如果图片生成失败,尝试发送纯文本消息 + if usage: + prompt_tokens = usage.get("prompt_tokens", 0) + completion_tokens = usage.get("completion_tokens", 0) + total_tokens = usage.get("total_tokens", 0) + + # 添加token信息 + tokens_info = f"\n\n【tokens】输入: {prompt_tokens} 生成: {completion_tokens} 总: {total_tokens}" + answer += tokens_info try: - # 截断过长的文本,避免消息太长 - max_length = 2000 - if len(answer) > max_length: - answer = answer[:max_length] + "\n\n... (内容过长,已截断)" - self.LOG.info("图片生成失败,将发送文本消息作为备选方案") - spath = None # 设置为None,让调用方知道需要发送文本 - except Exception as fallback_error: - self.LOG.error(f"备选文本发送也失败: {fallback_error}") - spath = None - # 返回文本内容和图片路径 - return answer, spath + # 使用唯一文件名并指定完整路径 + timestamp = int(time.time()) + output_path = f"summary_{timestamp}.png" + # 构建完整的输出路径 + self.LOG.info(f"开始生成图片: {output_path}") + spath = await convert_md_str_to_image(answer, output_path) + self.LOG.info(f"成功生成图片: {spath}") + except Exception as e: + self.LOG.error(f"生成图片失败: {e}", exc_info=True) + # 如果图片生成失败,尝试发送纯文本消息 + try: + # 截断过长的文本,避免消息太长 + max_length = 2000 + if len(answer) > max_length: + answer = answer[:max_length] + "\n\n... (内容过长,已截断)" + self.LOG.info("图片生成失败,将发送文本消息作为备选方案") + spath = None # 设置为None,让调用方知道需要发送文本 + except Exception as fallback_error: + self.LOG.error(f"备选文本发送也失败: {fallback_error}") + spath = None + # 返回文本内容和图片路径 + return answer, spath - except aiohttp.ClientError as e: - self.LOG.error(f"请求Dify API时出错: {e}") - return f"生成总结时出错", None + except aiohttp.ClientError as e: + self.LOG.error(f"请求Dify API时出错: attempt={attempt}/{max_retries}, error={e}") + except json.JSONDecodeError as e: + self.LOG.error(f"解析Dify API响应时出错: attempt={attempt}/{max_retries}, error={e}") + except Exception as e: + self.LOG.error(f"处理总结时出现未知错误: attempt={attempt}/{max_retries}, error={e}") - except json.JSONDecodeError as e: - self.LOG.error(f"解析Dify API响应时出错: {e}") - return "解析API响应时出错", None + if attempt < max_retries: + delay = retry_delays[attempt - 1] if attempt - 1 < len(retry_delays) else retry_delays[-1] + self.LOG.warning(f"群总结生成失败,准备重试: attempt={attempt}/{max_retries}, delay={delay}s") + await asyncio.sleep(delay) - except Exception as e: - self.LOG.error(f"处理总结时出现未知错误: {e}") - return f"生成总结时出现未知错误", None + return "生成总结时出错", None async def daily_summary_job(self): """定时任务:每天早上9点总结昨天的聊天信息"""