From 33606e70280e39e6178901a9e3611b8e48a8d467 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Thu, 24 Jul 2025 22:03:27 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat=20=E4=B8=BAfocus=E5=8A=A0=E5=85=A5=20m?= =?UTF-8?q?entioned=20bonus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/planner_actions/planner.py | 14 ++++++++++---- src/chat/replyer/default_generator.py | 9 ++++++++- src/config/config.py | 2 +- src/config/official_configs.py | 10 ++++++---- src/plugins/built_in/core_actions/reply.py | 7 +++++-- template/bot_config_template.toml | 6 +++--- 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 15eb7f9f3..97b4a5fdd 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -279,9 +279,15 @@ class ActionPlanner: self.last_obs_time_mark = time.time() if mode == ChatMode.FOCUS: + if global_config.normal_chat.mentioned_bot_inevitable_reply: + mentioned_bonus = "有人提到你" + if global_config.normal_chat.at_bot_inevitable_reply: + mentioned_bonus = "有人提到你,或者at你" + + by_what = "聊天内容" target_prompt = '\n "target_message_id":"触发action的消息id"' - no_action_block = """重要说明1: + no_action_block = f"""重要说明1: - 'no_reply' 表示只进行不进行回复,等待合适的回复时机 - 当你刚刚发送了消息,没有人回复时,选择no_reply - 当你一次发送了太多消息,为了避免打扰聊天节奏,选择no_reply @@ -289,13 +295,13 @@ class ActionPlanner: 动作:reply 动作描述:参与聊天回复,发送文本进行表达 - 你想要闲聊或者随便附和 -- 有人提到你 +- {mentioned_bonus} - 如果你刚刚进行了回复,不要对同一个话题重复回应 -{ +{{ "action": "reply", "target_message_id":"触发action的消息id", "reason":"回复的原因" -} +}} """ else: diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index ebdecb5c7..925f721aa 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -450,6 +450,9 @@ class DefaultReplyer: def _parse_reply_target(self, target_message: str) -> tuple: sender = "" target = "" + # 添加None检查,防止NoneType错误 + if target_message is None: + return sender, target if ":" in target_message or ":" in target_message: # 使用正则表达式匹配中文或英文冒号 parts = re.split(pattern=r"[::]", string=target_message, maxsplit=1) @@ -462,6 +465,10 @@ class DefaultReplyer: # 关键词检测与反应 keywords_reaction_prompt = "" try: + # 添加None检查,防止NoneType错误 + if target is None: + return keywords_reaction_prompt + # 处理关键词规则 for rule in global_config.keyword_reaction.keyword_rules: if any(keyword in target for keyword in rule.keywords): @@ -524,7 +531,7 @@ class DefaultReplyer: # 其他用户的对话 background_dialogue_list.append(msg_dict) except Exception as e: - logger.error(f"无法处理历史消息记录: {msg_dict}, 错误: {e}") + logger.error(f"![1753364551656](image/default_generator/1753364551656.png)记录: {msg_dict}, 错误: {e}") # 构建背景对话 prompt background_dialogue_prompt = "" diff --git a/src/config/config.py b/src/config/config.py index fae2ea2a4..247775c53 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -49,7 +49,7 @@ TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "template") # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 # 对该字段的更新,请严格参照语义化版本规范:https://semver.org/lang/zh-CN/ -MMC_VERSION = "0.9.0" +MMC_VERSION = "0.9.1-snapshot.1" def get_key_comment(toml_table, key): diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 1a14b47ce..e19517f18 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -84,6 +84,12 @@ class ChatConfig(ConfigBase): use_s4u_prompt_mode: bool = False """是否使用 s4u 对话构建模式,该模式会分开处理当前对话对象和其他所有对话的内容进行 prompt 构建""" + mentioned_bot_inevitable_reply: bool = False + """提及 bot 必然回复""" + + at_bot_inevitable_reply: bool = False + """@bot 必然回复""" + # 修改:基于时段的回复频率配置,改为数组格式 time_based_talk_frequency: list[str] = field(default_factory=lambda: []) """ @@ -270,11 +276,7 @@ class NormalChatConfig(ConfigBase): response_interested_rate_amplifier: float = 1.0 """回复兴趣度放大系数""" - mentioned_bot_inevitable_reply: bool = False - """提及 bot 必然回复""" - at_bot_inevitable_reply: bool = False - """@bot 必然回复""" @dataclass diff --git a/src/plugins/built_in/core_actions/reply.py b/src/plugins/built_in/core_actions/reply.py index 644534dba..d73337b29 100644 --- a/src/plugins/built_in/core_actions/reply.py +++ b/src/plugins/built_in/core_actions/reply.py @@ -32,13 +32,13 @@ class ReplyAction(BaseAction): # 动作基本信息 action_name = "reply" - action_description = "参与聊天回复,发送文本进行表达" + action_description = "" # 动作参数定义 action_parameters = {} # 动作使用场景 - action_require = ["你想要闲聊或者随便附和", "有人提到你", "如果你刚刚进行了回复,不要对同一个话题重复回应"] + action_require = [""] # 关联类型 associated_types = ["text"] @@ -46,6 +46,9 @@ class ReplyAction(BaseAction): def _parse_reply_target(self, target_message: str) -> tuple: sender = "" target = "" + # 添加None检查,防止NoneType错误 + if target_message is None: + return sender, target if ":" in target_message or ":" in target_message: # 使用正则表达式匹配中文或英文冒号 parts = re.split(pattern=r"[::]", string=target_message, maxsplit=1) diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 9e83574ee..b5678fb7c 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -59,6 +59,9 @@ max_context_size = 25 # 上下文长度 thinking_timeout = 20 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢) replyer_random_probability = 0.5 # 首要replyer模型被选择的概率 +mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复 +at_bot_inevitable_reply = true # @bot 或 提及bot 大概率回复 + use_s4u_prompt_mode = true # 是否使用 s4u 对话构建模式,该模式会更好的把握当前对话对象的对话内容,但是对群聊整理理解能力较差(测试功能!!可能有未知问题!!) @@ -101,11 +104,8 @@ ban_msgs_regex = [ ] [normal_chat] #普通聊天 -#一般回复参数 willing_mode = "classical" # 回复意愿模式 —— 经典模式:classical,mxp模式:mxp,自定义模式:custom(需要你自己实现) response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数 -mentioned_bot_inevitable_reply = true # 提及 bot 必然回复 -at_bot_inevitable_reply = true # @bot 必然回复(包含提及) [tool] enable_in_normal_chat = false # 是否在普通聊天中启用工具 From 16b125b815cd7974bc59ce2ac552d18e6c3481ac Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Thu, 24 Jul 2025 22:16:21 +0800 Subject: [PATCH 2/5] Update expression_learner.py --- src/chat/express/expression_learner.py | 100 +++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/src/chat/express/expression_learner.py b/src/chat/express/expression_learner.py index 4afcfe7dd..ac41b12a3 100644 --- a/src/chat/express/expression_learner.py +++ b/src/chat/express/expression_learner.py @@ -87,36 +87,90 @@ class ExpressionLearner: request_type="expressor.learner", ) self.llm_model = None + self._ensure_expression_directories() self._auto_migrate_json_to_db() self._migrate_old_data_create_date() + def _ensure_expression_directories(self): + """ + 确保表达方式相关的目录结构存在 + """ + base_dir = os.path.join("data", "expression") + directories_to_create = [ + base_dir, + os.path.join(base_dir, "learnt_style"), + os.path.join(base_dir, "learnt_grammar"), + ] + + for directory in directories_to_create: + try: + os.makedirs(directory, exist_ok=True) + logger.debug(f"确保目录存在: {directory}") + except Exception as e: + logger.error(f"创建目录失败 {directory}: {e}") + def _auto_migrate_json_to_db(self): """ 自动将/data/expression/learnt_style 和 learnt_grammar 下所有expressions.json迁移到数据库。 迁移完成后在/data/expression/done.done写入标记文件,存在则跳过。 """ - done_flag = os.path.join("data", "expression", "done.done") + base_dir = os.path.join("data", "expression") + done_flag = os.path.join(base_dir, "done.done") + + # 确保基础目录存在 + try: + os.makedirs(base_dir, exist_ok=True) + logger.debug(f"确保目录存在: {base_dir}") + except Exception as e: + logger.error(f"创建表达方式目录失败: {e}") + return + if os.path.exists(done_flag): logger.info("表达方式JSON已迁移,无需重复迁移。") return - base_dir = os.path.join("data", "expression") + + logger.info("开始迁移表达方式JSON到数据库...") + migrated_count = 0 + for type in ["learnt_style", "learnt_grammar"]: type_str = "style" if type == "learnt_style" else "grammar" type_dir = os.path.join(base_dir, type) if not os.path.exists(type_dir): + logger.debug(f"目录不存在,跳过: {type_dir}") continue - for chat_id in os.listdir(type_dir): + + try: + chat_ids = os.listdir(type_dir) + logger.debug(f"在 {type_dir} 中找到 {len(chat_ids)} 个聊天ID目录") + except Exception as e: + logger.error(f"读取目录失败 {type_dir}: {e}") + continue + + for chat_id in chat_ids: expr_file = os.path.join(type_dir, chat_id, "expressions.json") if not os.path.exists(expr_file): continue try: with open(expr_file, "r", encoding="utf-8") as f: expressions = json.load(f) + + if not isinstance(expressions, list): + logger.warning(f"表达方式文件格式错误,跳过: {expr_file}") + continue + for expr in expressions: + if not isinstance(expr, dict): + continue + situation = expr.get("situation") style_val = expr.get("style") count = expr.get("count", 1) last_active_time = expr.get("last_active_time", time.time()) + + if not situation or not style_val: + logger.warning(f"表达方式缺少必要字段,跳过: {expr}") + continue + # 查重:同chat_id+type+situation+style from src.common.database.database_model import Expression @@ -141,14 +195,28 @@ class ExpressionLearner: type=type_str, create_date=last_active_time, # 迁移时使用last_active_time作为创建时间 ) - logger.info(f"已迁移 {expr_file} 到数据库") + migrated_count += 1 + logger.info(f"已迁移 {expr_file} 到数据库,包含 {len(expressions)} 个表达方式") + except json.JSONDecodeError as e: + logger.error(f"JSON解析失败 {expr_file}: {e}") except Exception as e: logger.error(f"迁移表达方式 {expr_file} 失败: {e}") + # 标记迁移完成 try: + # 确保done.done文件的父目录存在 + done_parent_dir = os.path.dirname(done_flag) + if not os.path.exists(done_parent_dir): + os.makedirs(done_parent_dir, exist_ok=True) + logger.debug(f"为done.done创建父目录: {done_parent_dir}") + with open(done_flag, "w", encoding="utf-8") as f: f.write("done\n") - logger.info("表达方式JSON迁移已完成,已写入done.done标记文件") + logger.info(f"表达方式JSON迁移已完成,共迁移 {migrated_count} 个表达方式,已写入done.done标记文件") + except PermissionError as e: + logger.error(f"权限不足,无法写入done.done标记文件: {e}") + except OSError as e: + logger.error(f"文件系统错误,无法写入done.done标记文件: {e}") except Exception as e: logger.error(f"写入done.done标记文件失败: {e}") @@ -266,9 +334,17 @@ class ExpressionLearner: for type in ["style", "grammar"]: base_dir = os.path.join("data", "expression", f"learnt_{type}") if not os.path.exists(base_dir): + logger.debug(f"目录不存在,跳过衰减: {base_dir}") continue - for chat_id in os.listdir(base_dir): + try: + chat_ids = os.listdir(base_dir) + logger.debug(f"在 {base_dir} 中找到 {len(chat_ids)} 个聊天ID目录进行衰减") + except Exception as e: + logger.error(f"读取目录失败 {base_dir}: {e}") + continue + + for chat_id in chat_ids: file_path = os.path.join(base_dir, chat_id, "expressions.json") if not os.path.exists(file_path): continue @@ -277,14 +353,24 @@ class ExpressionLearner: with open(file_path, "r", encoding="utf-8") as f: expressions = json.load(f) + if not isinstance(expressions, list): + logger.warning(f"表达方式文件格式错误,跳过衰减: {file_path}") + continue + # 应用全局衰减 decayed_expressions = self.apply_decay_to_expressions(expressions, current_time) # 保存衰减后的结果 with open(file_path, "w", encoding="utf-8") as f: json.dump(decayed_expressions, f, ensure_ascii=False, indent=2) + + logger.debug(f"已对 {file_path} 应用衰减,剩余 {len(decayed_expressions)} 个表达方式") + except json.JSONDecodeError as e: + logger.error(f"JSON解析失败,跳过衰减 {file_path}: {e}") + except PermissionError as e: + logger.error(f"权限不足,无法更新 {file_path}: {e}") except Exception as e: - logger.error(f"全局衰减{type}表达方式失败: {e}") + logger.error(f"全局衰减{type}表达方式失败 {file_path}: {e}") continue learnt_style: Optional[List[Tuple[str, str, str]]] = [] From db896299bedbc0e84c25323c4301cf480846dffd Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Thu, 24 Jul 2025 22:30:27 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- changelogs/changelog.md | 8 ++++++++ src/chat/planner_actions/planner.py | 12 ++++++------ src/chat/utils/utils.py | 2 +- src/chat/willing/mode_classical.py | 2 +- src/config/config.py | 2 +- 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 81bb4d087..3a9e14f80 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ ## 🔥 更新和安装 -**最新版本: v0.9.0** ([更新日志](changelogs/changelog.md)) +**最新版本: v0.9.1** ([更新日志](changelogs/changelog.md)) 可前往 [Release](https://github.com/MaiM-with-u/MaiBot/releases/) 页面下载最新版本 可前往 [启动器发布页面](https://github.com/MaiM-with-u/mailauncher/releases/)下载最新启动器 diff --git a/changelogs/changelog.md b/changelogs/changelog.md index e53ba6ed6..c56426a72 100644 --- a/changelogs/changelog.md +++ b/changelogs/changelog.md @@ -1,5 +1,13 @@ # Changelog +## [0.9.1] - 2025-7-25 + +- 修复表达方式迁移空目录问题 +- 修复reply_to空字段问题 +- 将metioned bot 和 at应用到focus prompt中 + + + ## [0.9.0] - 2025-7-25 ### 摘要 diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 97b4a5fdd..e3d1edef9 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -279,10 +279,11 @@ class ActionPlanner: self.last_obs_time_mark = time.time() if mode == ChatMode.FOCUS: - if global_config.normal_chat.mentioned_bot_inevitable_reply: - mentioned_bonus = "有人提到你" - if global_config.normal_chat.at_bot_inevitable_reply: - mentioned_bonus = "有人提到你,或者at你" + mentioned_bonus = "" + if global_config.chat.mentioned_bot_inevitable_reply: + mentioned_bonus = "\n- 有人提到你" + if global_config.chat.at_bot_inevitable_reply: + mentioned_bonus = "\n- 有人提到你,或者at你" by_what = "聊天内容" @@ -294,8 +295,7 @@ class ActionPlanner: 动作:reply 动作描述:参与聊天回复,发送文本进行表达 -- 你想要闲聊或者随便附和 -- {mentioned_bonus} +- 你想要闲聊或者随便附和{mentioned_bonus} - 如果你刚刚进行了回复,不要对同一个话题重复回应 {{ "action": "reply", diff --git a/src/chat/utils/utils.py b/src/chat/utils/utils.py index e7d2caddb..13ffc2fda 100644 --- a/src/chat/utils/utils.py +++ b/src/chat/utils/utils.py @@ -103,7 +103,7 @@ def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]: for nickname in nicknames: if nickname in message_content: is_mentioned = True - if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply: + if is_mentioned and global_config.chat.mentioned_bot_inevitable_reply: reply_probability = 1.0 logger.debug("被提及,回复概率设置为100%") return is_mentioned, reply_probability diff --git a/src/chat/willing/mode_classical.py b/src/chat/willing/mode_classical.py index e15272332..57400c44d 100644 --- a/src/chat/willing/mode_classical.py +++ b/src/chat/willing/mode_classical.py @@ -35,7 +35,7 @@ class ClassicalWillingManager(BaseWillingManager): if interested_rate > 0.2: current_willing += interested_rate - 0.2 - if willing_info.is_mentioned_bot and global_config.normal_chat.mentioned_bot_inevitable_reply and current_willing < 2: + if willing_info.is_mentioned_bot and global_config.chat.mentioned_bot_inevitable_reply and current_willing < 2: current_willing += 1 if current_willing < 1.0 else 0.05 self.chat_reply_willing[chat_id] = min(current_willing, 1.0) diff --git a/src/config/config.py b/src/config/config.py index 247775c53..805a17d48 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -49,7 +49,7 @@ TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "template") # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 # 对该字段的更新,请严格参照语义化版本规范:https://semver.org/lang/zh-CN/ -MMC_VERSION = "0.9.1-snapshot.1" +MMC_VERSION = "0.9.1" def get_key_comment(toml_table, key): From 8de3963069f3f02c6ddf4331b0554c79a67cdb9e Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Thu, 24 Jul 2025 22:47:13 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat=20=E7=BB=9F=E4=B8=80=E5=BF=83=E6=83=85?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=EF=BC=8C=E4=B8=BArewartite=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E5=BF=83=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../heart_flow/heartflow_message_processor.py | 6 +++--- src/chat/replyer/default_generator.py | 18 +++++++++++++++--- src/config/official_configs.py | 12 +++--------- src/mood/mood_manager.py | 2 +- template/bot_config_template.toml | 4 +--- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index a9d118286..3aa174bb5 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -111,9 +111,9 @@ class HeartFCMessageReceiver: subheartflow: SubHeartflow = await heartflow.get_or_create_subheartflow(chat.stream_id) # type: ignore # subheartflow.add_message_to_normal_chat_cache(message, interested_rate, is_mentioned) - - chat_mood = mood_manager.get_mood_by_chat_id(subheartflow.chat_id) - asyncio.create_task(chat_mood.update_mood_by_message(message, interested_rate)) + if global_config.mood.enable_mood: + chat_mood = mood_manager.get_mood_by_chat_id(subheartflow.chat_id) + asyncio.create_task(chat_mood.update_mood_by_message(message, interested_rate)) # 3. 日志记录 mes_name = chat.group_info.group_name if chat.group_info else "私聊" diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 925f721aa..9d75671c6 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -74,6 +74,7 @@ def init_prompt(): 你正在{chat_target_2},{reply_target_block} 对这句话,你想表达,原句:{raw_reply},原因是:{reason}。你现在要思考怎么组织回复 +你现在的心情是:{mood_state} 你需要使用合适的语法和句法,参考聊天内容,组织一条日常且口语化的回复。请你修改你想表达的原句,符合你的表达风格和语言习惯 {config_expression_style},你可以完全重组回复,保留最基本的表达含义就好,但重组后保持语意通顺。 {keywords_reaction_prompt} @@ -620,9 +621,12 @@ class DefaultReplyer: is_group_chat = bool(chat_stream.group_info) reply_to = reply_data.get("reply_to", "none") extra_info_block = reply_data.get("extra_info", "") or reply_data.get("extra_info_block", "") - - chat_mood = mood_manager.get_mood_by_chat_id(chat_id) - mood_prompt = chat_mood.mood_state + + if global_config.mood.enable_mood: + chat_mood = mood_manager.get_mood_by_chat_id(chat_id) + mood_prompt = chat_mood.mood_state + else: + mood_prompt = "" sender, target = self._parse_reply_target(reply_to) @@ -883,6 +887,13 @@ class DefaultReplyer: reason = reply_data.get("reason", "") sender, target = self._parse_reply_target(reply_to) + # 添加情绪状态获取 + if global_config.mood.enable_mood: + chat_mood = mood_manager.get_mood_by_chat_id(chat_id) + mood_prompt = chat_mood.mood_state + else: + mood_prompt = "" + message_list_before_now_half = get_raw_msg_before_timestamp_with_chat( chat_id=chat_id, timestamp=time.time(), @@ -963,6 +974,7 @@ class DefaultReplyer: reply_target_block=reply_target_block, raw_reply=raw_reply, reason=reason, + mood_state=mood_prompt, # 添加情绪状态参数 config_expression_style=global_config.expression.expression_style, keywords_reaction_prompt=keywords_reaction_prompt, moderation_prompt=moderation_prompt_block, diff --git a/src/config/official_configs.py b/src/config/official_configs.py index e19517f18..82284d9b3 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -408,15 +408,9 @@ class MoodConfig(ConfigBase): enable_mood: bool = False """是否启用情绪系统""" - - mood_update_interval: int = 1 - """情绪更新间隔(秒)""" - - mood_decay_rate: float = 0.95 - """情绪衰减率""" - - mood_intensity_factor: float = 0.7 - """情绪强度因子""" + + mood_update_threshold: float = 1.0 + """情绪更新阈值,越高,更新越慢""" @dataclass diff --git a/src/mood/mood_manager.py b/src/mood/mood_manager.py index 88c827921..38ed39bcc 100644 --- a/src/mood/mood_manager.py +++ b/src/mood/mood_manager.py @@ -83,7 +83,7 @@ class ChatMood: logger.debug( f"base_probability: {base_probability}, time_multiplier: {time_multiplier}, interest_multiplier: {interest_multiplier}" ) - update_probability = min(1.0, base_probability * time_multiplier * interest_multiplier) + update_probability = global_config.mood.mood_update_threshold * min(1.0, base_probability * time_multiplier * interest_multiplier) if random.random() > update_probability: return diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index b5678fb7c..ff8a79e73 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -148,9 +148,7 @@ enable_asr = false # 是否启用语音识别,启用后麦麦可以识别语 [mood] enable_mood = true # 是否启用情绪系统 -mood_update_interval = 1.0 # 情绪更新间隔 单位秒 -mood_decay_rate = 0.95 # 情绪衰减率 -mood_intensity_factor = 1.0 # 情绪强度因子 +mood_update_threshold = 1 # 情绪更新阈值,越高,更新越慢 [lpmm_knowledge] # lpmm知识库配置 enable = false # 是否启用lpmm知识库 From 4ab6d59a79135b88dadfb906470436b50decb9c5 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Thu, 24 Jul 2025 23:20:05 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8Denable=5Fthin?= =?UTF-8?q?king=E5=AF=BC=E8=87=B4=E7=9A=84400=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/utils/utils.py | 2 +- src/llm_models/utils_model.py | 126 +++++++++++++++++++++++++++++++--- 2 files changed, 117 insertions(+), 11 deletions(-) diff --git a/src/chat/utils/utils.py b/src/chat/utils/utils.py index 13ffc2fda..3ee4ae7b1 100644 --- a/src/chat/utils/utils.py +++ b/src/chat/utils/utils.py @@ -78,7 +78,7 @@ def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]: # print(f"is_mentioned: {is_mentioned}") # print(f"is_at: {is_at}") - if is_at and global_config.normal_chat.at_bot_inevitable_reply: + if is_at and global_config.chat.at_bot_inevitable_reply: reply_probability = 1.0 logger.debug("被@,回复概率设置为100%") else: diff --git a/src/llm_models/utils_model.py b/src/llm_models/utils_model.py index 3621b4502..8a1215884 100644 --- a/src/llm_models/utils_model.py +++ b/src/llm_models/utils_model.py @@ -109,10 +109,15 @@ class LLMRequest: def __init__(self, model: dict, **kwargs): # 将大写的配置键转换为小写并从config中获取实际值 + logger.debug(f"🔍 [模型初始化] 开始初始化模型: {model.get('name', 'Unknown')}") + logger.debug(f"🔍 [模型初始化] 模型配置: {model}") + logger.debug(f"🔍 [模型初始化] 额外参数: {kwargs}") + try: # print(f"model['provider']: {model['provider']}") self.api_key = os.environ[f"{model['provider']}_KEY"] self.base_url = os.environ[f"{model['provider']}_BASE_URL"] + logger.debug(f"🔍 [模型初始化] 成功获取环境变量: {model['provider']}_KEY 和 {model['provider']}_BASE_URL") except AttributeError as e: logger.error(f"原始 model dict 信息:{model}") logger.error(f"配置错误:找不到对应的配置项 - {str(e)}") @@ -124,6 +129,10 @@ class LLMRequest: self.model_name: str = model["name"] self.params = kwargs + # 记录配置文件中声明了哪些参数(不管值是什么) + self.has_enable_thinking = "enable_thinking" in model + self.has_thinking_budget = "thinking_budget" in model + self.enable_thinking = model.get("enable_thinking", False) self.temp = model.get("temp", 0.7) self.thinking_budget = model.get("thinking_budget", 4096) @@ -132,12 +141,24 @@ class LLMRequest: self.pri_out = model.get("pri_out", 0) self.max_tokens = model.get("max_tokens", global_config.model.model_max_output_length) # print(f"max_tokens: {self.max_tokens}") + + logger.debug(f"🔍 [模型初始化] 模型参数设置完成:") + logger.debug(f" - model_name: {self.model_name}") + logger.debug(f" - has_enable_thinking: {self.has_enable_thinking}") + logger.debug(f" - enable_thinking: {self.enable_thinking}") + logger.debug(f" - has_thinking_budget: {self.has_thinking_budget}") + logger.debug(f" - thinking_budget: {self.thinking_budget}") + logger.debug(f" - temp: {self.temp}") + logger.debug(f" - stream: {self.stream}") + logger.debug(f" - max_tokens: {self.max_tokens}") + logger.debug(f" - base_url: {self.base_url}") # 获取数据库实例 self._init_database() # 从 kwargs 中提取 request_type,如果没有提供则默认为 "default" self.request_type = kwargs.pop("request_type", "default") + logger.debug(f"🔍 [模型初始化] 初始化完成,request_type: {self.request_type}") @staticmethod def _init_database(): @@ -262,11 +283,12 @@ class LLMRequest: if self.temp != 0.7: payload["temperature"] = self.temp - # 添加enable_thinking参数(如果不是默认值False) - if not self.enable_thinking: - payload["enable_thinking"] = False + # 添加enable_thinking参数(只有配置文件中声明了才添加,不管值是true还是false) + if self.has_enable_thinking: + payload["enable_thinking"] = self.enable_thinking - if self.thinking_budget != 4096: + # 添加thinking_budget参数(只有配置文件中声明了才添加) + if self.has_thinking_budget: payload["thinking_budget"] = self.thinking_budget if self.max_tokens: @@ -334,6 +356,19 @@ class LLMRequest: # 似乎是openai流式必须要的东西,不过阿里云的qwq-plus加了这个没有影响 if request_content["stream_mode"]: headers["Accept"] = "text/event-stream" + + # 添加请求发送前的调试信息 + logger.debug(f"🔍 [请求调试] 模型 {self.model_name} 准备发送请求") + logger.debug(f"🔍 [请求调试] API URL: {request_content['api_url']}") + logger.debug(f"🔍 [请求调试] 请求头: {await self._build_headers(no_key=True, is_formdata=file_bytes is not None)}") + + if not file_bytes: + # 安全地记录请求体(隐藏敏感信息) + safe_payload = await _safely_record(request_content, request_content["payload"]) + logger.debug(f"🔍 [请求调试] 请求体: {json.dumps(safe_payload, indent=2, ensure_ascii=False)}") + else: + logger.debug(f"🔍 [请求调试] 文件上传请求,文件格式: {request_content['file_format']}") + async with aiohttp.ClientSession(connector=await get_tcp_connector()) as session: post_kwargs = {"headers": headers} # form-data数据上传方式不同 @@ -491,7 +526,36 @@ class LLMRequest: logger.warning(f"模型 {self.model_name} 请求限制(429),等待{wait_time}秒后重试...") raise RuntimeError("请求限制(429)") elif response.status in policy["abort_codes"]: - if response.status != 403: + # 特别处理400错误,添加详细调试信息 + if response.status == 400: + logger.error(f"🔍 [调试信息] 模型 {self.model_name} 参数错误 (400) - 开始详细诊断") + logger.error(f"🔍 [调试信息] 模型名称: {self.model_name}") + logger.error(f"🔍 [调试信息] API地址: {self.base_url}") + logger.error(f"🔍 [调试信息] 模型配置参数:") + logger.error(f" - enable_thinking: {self.enable_thinking}") + logger.error(f" - temp: {self.temp}") + logger.error(f" - thinking_budget: {self.thinking_budget}") + logger.error(f" - stream: {self.stream}") + logger.error(f" - max_tokens: {self.max_tokens}") + logger.error(f" - pri_in: {self.pri_in}") + logger.error(f" - pri_out: {self.pri_out}") + logger.error(f"🔍 [调试信息] 原始params: {self.params}") + + # 尝试获取服务器返回的详细错误信息 + try: + error_text = await response.text() + logger.error(f"🔍 [调试信息] 服务器返回的原始错误内容: {error_text}") + + try: + error_json = json.loads(error_text) + logger.error(f"🔍 [调试信息] 解析后的错误JSON: {json.dumps(error_json, indent=2, ensure_ascii=False)}") + except json.JSONDecodeError: + logger.error(f"🔍 [调试信息] 错误响应不是有效的JSON格式") + except Exception as e: + logger.error(f"🔍 [调试信息] 无法读取错误响应内容: {str(e)}") + + raise RequestAbortException("参数错误,请检查调试信息", response) + elif response.status != 403: raise RequestAbortException("请求出现错误,中断处理", response) else: raise PermissionDeniedException("模型禁止访问") @@ -510,6 +574,19 @@ class LLMRequest: logger.error( f"模型 {self.model_name} 错误码: {response.status} - {error_code_mapping.get(response.status)}" ) + + # 如果是400错误,额外输出请求体信息用于调试 + if response.status == 400: + logger.error(f"🔍 [异常调试] 400错误 - 请求体调试信息:") + try: + safe_payload = await _safely_record(request_content, payload) + logger.error(f"🔍 [异常调试] 发送的请求体: {json.dumps(safe_payload, indent=2, ensure_ascii=False)}") + except Exception as debug_error: + logger.error(f"🔍 [异常调试] 无法安全记录请求体: {str(debug_error)}") + logger.error(f"🔍 [异常调试] 原始payload类型: {type(payload)}") + if isinstance(payload, dict): + logger.error(f"🔍 [异常调试] 原始payload键: {list(payload.keys())}") + # print(request_content) # print(response) # 尝试获取并记录服务器返回的详细错误信息 @@ -654,14 +731,27 @@ class LLMRequest: """ # 复制一份参数,避免直接修改原始数据 new_params = dict(params) + + logger.debug(f"🔍 [参数转换] 模型 {self.model_name} 开始参数转换") + logger.debug(f"🔍 [参数转换] 是否为CoT模型: {self.model_name.lower() in self.MODELS_NEEDING_TRANSFORMATION}") + logger.debug(f"🔍 [参数转换] CoT模型列表: {self.MODELS_NEEDING_TRANSFORMATION}") if self.model_name.lower() in self.MODELS_NEEDING_TRANSFORMATION: + logger.debug(f"🔍 [参数转换] 检测到CoT模型,开始参数转换") # 删除 'temperature' 参数(如果存在),但避免删除我们在_build_payload中添加的自定义温度 if "temperature" in new_params and new_params["temperature"] == 0.7: - new_params.pop("temperature") + removed_temp = new_params.pop("temperature") + logger.debug(f"🔍 [参数转换] 移除默认temperature参数: {removed_temp}") # 如果存在 'max_tokens',则重命名为 'max_completion_tokens' if "max_tokens" in new_params: + old_value = new_params["max_tokens"] new_params["max_completion_tokens"] = new_params.pop("max_tokens") + logger.debug(f"🔍 [参数转换] 参数重命名: max_tokens({old_value}) -> max_completion_tokens({new_params['max_completion_tokens']})") + else: + logger.debug(f"🔍 [参数转换] 非CoT模型,无需参数转换") + + logger.debug(f"🔍 [参数转换] 转换前参数: {params}") + logger.debug(f"🔍 [参数转换] 转换后参数: {new_params}") return new_params async def _build_formdata_payload(self, file_bytes: bytes, file_format: str) -> aiohttp.FormData: @@ -693,7 +783,12 @@ class LLMRequest: async def _build_payload(self, prompt: str, image_base64: str = None, image_format: str = None) -> dict: """构建请求体""" # 复制一份参数,避免直接修改 self.params + logger.debug(f"🔍 [参数构建] 模型 {self.model_name} 开始构建请求体") + logger.debug(f"🔍 [参数构建] 原始self.params: {self.params}") + params_copy = await self._transform_parameters(self.params) + logger.debug(f"🔍 [参数构建] 转换后的params_copy: {params_copy}") + if image_base64: messages = [ { @@ -715,26 +810,37 @@ class LLMRequest: "messages": messages, **params_copy, } + + logger.debug(f"🔍 [参数构建] 基础payload构建完成: {list(payload.keys())}") # 添加temp参数(如果不是默认值0.7) if self.temp != 0.7: payload["temperature"] = self.temp + logger.debug(f"🔍 [参数构建] 添加temperature参数: {self.temp}") - # 添加enable_thinking参数(如果不是默认值False) - if not self.enable_thinking: - payload["enable_thinking"] = False + # 添加enable_thinking参数(只有配置文件中声明了才添加,不管值是true还是false) + if self.has_enable_thinking: + payload["enable_thinking"] = self.enable_thinking + logger.debug(f"🔍 [参数构建] 添加enable_thinking参数: {self.enable_thinking}") - if self.thinking_budget != 4096: + # 添加thinking_budget参数(只有配置文件中声明了才添加) + if self.has_thinking_budget: payload["thinking_budget"] = self.thinking_budget + logger.debug(f"🔍 [参数构建] 添加thinking_budget参数: {self.thinking_budget}") if self.max_tokens: payload["max_tokens"] = self.max_tokens + logger.debug(f"🔍 [参数构建] 添加max_tokens参数: {self.max_tokens}") # if "max_tokens" not in payload and "max_completion_tokens" not in payload: # payload["max_tokens"] = global_config.model.model_max_output_length # 如果 payload 中依然存在 max_tokens 且需要转换,在这里进行再次检查 if self.model_name.lower() in self.MODELS_NEEDING_TRANSFORMATION and "max_tokens" in payload: + old_value = payload["max_tokens"] payload["max_completion_tokens"] = payload.pop("max_tokens") + logger.debug(f"🔍 [参数构建] CoT模型参数转换: max_tokens({old_value}) -> max_completion_tokens({payload['max_completion_tokens']})") + + logger.debug(f"🔍 [参数构建] 最终payload键列表: {list(payload.keys())}") return payload def _default_response_handler(