From 5bde31e5123e6b1384fd15fa954d640008479e04 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 5 Nov 2025 00:35:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E9=BB=91=E8=AF=9D?= =?UTF-8?q?=E6=94=B6=E9=9B=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/replyer/group_generator.py | 83 +++++++++++++++++++++++ src/chat/replyer/private_generator.py | 83 +++++++++++++++++++++++ src/chat/replyer/prompt/replyer_prompt.py | 6 +- src/config/official_configs.py | 19 ++++++ template/bot_config_template.toml | 12 +++- 5 files changed, 199 insertions(+), 4 deletions(-) diff --git a/src/chat/replyer/group_generator.py b/src/chat/replyer/group_generator.py index cc6dfee4..6f0a944d 100644 --- a/src/chat/replyer/group_generator.py +++ b/src/chat/replyer/group_generator.py @@ -639,6 +639,83 @@ class DefaultReplyer: prompt_personality = f"{prompt_personality};" return f"你的名字是{bot_name}{bot_nickname},你{prompt_personality}" + def _parse_chat_prompt_config_to_chat_id(self, chat_prompt_str: str) -> Optional[tuple[str, str]]: + """ + 解析聊天prompt配置字符串并生成对应的 chat_id 和 prompt内容 + + Args: + chat_prompt_str: 格式为 "platform:id:type:prompt内容" 的字符串 + + Returns: + tuple: (chat_id, prompt_content),如果解析失败则返回 None + """ + try: + # 使用 split 分割,但限制分割次数为3,因为prompt内容可能包含冒号 + parts = chat_prompt_str.split(":", 3) + if len(parts) != 4: + return None + + platform = parts[0] + id_str = parts[1] + stream_type = parts[2] + prompt_content = parts[3] + + # 判断是否为群聊 + is_group = stream_type == "group" + + # 使用与 ChatStream.get_stream_id 相同的逻辑生成 chat_id + import hashlib + + if is_group: + components = [platform, str(id_str)] + else: + components = [platform, str(id_str), "private"] + key = "_".join(components) + chat_id = hashlib.md5(key.encode()).hexdigest() + + return chat_id, prompt_content + + except (ValueError, IndexError): + return None + + def get_chat_prompt_for_chat(self, chat_id: str) -> str: + """ + 根据聊天流ID获取匹配的额外prompt(仅匹配group类型) + + Args: + chat_id: 聊天流ID(哈希值) + + Returns: + str: 匹配的额外prompt内容,如果没有匹配则返回空字符串 + """ + if not global_config.experimental.chat_prompts: + return "" + + for chat_prompt_str in global_config.experimental.chat_prompts: + if not isinstance(chat_prompt_str, str): + continue + + # 解析配置字符串,检查类型是否为group + parts = chat_prompt_str.split(":", 3) + if len(parts) != 4: + continue + + stream_type = parts[2] + # 只匹配group类型 + if stream_type != "group": + continue + + result = self._parse_chat_prompt_config_to_chat_id(chat_prompt_str) + if result is None: + continue + + config_chat_id, prompt_content = result + if config_chat_id == chat_id: + logger.debug(f"匹配到群聊prompt配置,chat_id: {chat_id}, prompt: {prompt_content[:50]}...") + return prompt_content + + return "" + async def build_prompt_reply_context( self, reply_message: Optional[DatabaseMessages] = None, @@ -820,6 +897,11 @@ class DefaultReplyer: # 构建分离的对话 prompt dialogue_prompt = self.build_chat_history_prompts(message_list_before_now_long, user_id, sender) + # 获取匹配的额外prompt + chat_prompt_content = self.get_chat_prompt_for_chat(chat_id) + chat_prompt_block = f"{chat_prompt_content}\n" if chat_prompt_content else "" + + # 固定使用群聊回复模板 return await global_prompt_manager.format_prompt( "replyer_prompt", expression_habits_block=expression_habits_block, @@ -840,6 +922,7 @@ class DefaultReplyer: keywords_reaction_prompt=keywords_reaction_prompt, moderation_prompt=moderation_prompt_block, question_block=question_block, + chat_prompt=chat_prompt_block, ), selected_expressions async def build_prompt_rewrite_context( diff --git a/src/chat/replyer/private_generator.py b/src/chat/replyer/private_generator.py index 2bd48de4..8a92fd15 100644 --- a/src/chat/replyer/private_generator.py +++ b/src/chat/replyer/private_generator.py @@ -536,6 +536,83 @@ class PrivateReplyer: prompt_personality = f"{prompt_personality};" return f"你的名字是{bot_name}{bot_nickname},你{prompt_personality}" + def _parse_chat_prompt_config_to_chat_id(self, chat_prompt_str: str) -> Optional[tuple[str, str]]: + """ + 解析聊天prompt配置字符串并生成对应的 chat_id 和 prompt内容 + + Args: + chat_prompt_str: 格式为 "platform:id:type:prompt内容" 的字符串 + + Returns: + tuple: (chat_id, prompt_content),如果解析失败则返回 None + """ + try: + # 使用 split 分割,但限制分割次数为3,因为prompt内容可能包含冒号 + parts = chat_prompt_str.split(":", 3) + if len(parts) != 4: + return None + + platform = parts[0] + id_str = parts[1] + stream_type = parts[2] + prompt_content = parts[3] + + # 判断是否为群聊 + is_group = stream_type == "group" + + # 使用与 ChatStream.get_stream_id 相同的逻辑生成 chat_id + import hashlib + + if is_group: + components = [platform, str(id_str)] + else: + components = [platform, str(id_str), "private"] + key = "_".join(components) + chat_id = hashlib.md5(key.encode()).hexdigest() + + return chat_id, prompt_content + + except (ValueError, IndexError): + return None + + def get_chat_prompt_for_chat(self, chat_id: str) -> str: + """ + 根据聊天流ID获取匹配的额外prompt(仅匹配private类型) + + Args: + chat_id: 聊天流ID(哈希值) + + Returns: + str: 匹配的额外prompt内容,如果没有匹配则返回空字符串 + """ + if not global_config.experimental.chat_prompts: + return "" + + for chat_prompt_str in global_config.experimental.chat_prompts: + if not isinstance(chat_prompt_str, str): + continue + + # 解析配置字符串,检查类型是否为private + parts = chat_prompt_str.split(":", 3) + if len(parts) != 4: + continue + + stream_type = parts[2] + # 只匹配private类型 + if stream_type != "private": + continue + + result = self._parse_chat_prompt_config_to_chat_id(chat_prompt_str) + if result is None: + continue + + config_chat_id, prompt_content = result + if config_chat_id == chat_id: + logger.debug(f"匹配到私聊prompt配置,chat_id: {chat_id}, prompt: {prompt_content[:50]}...") + return prompt_content + + return "" + async def build_prompt_reply_context( self, reply_message: Optional[DatabaseMessages] = None, @@ -718,6 +795,10 @@ class PrivateReplyer: # 其他情况(空内容等) reply_target_block = f"现在对方说的:{target}。引起了你的注意" + # 获取匹配的额外prompt + chat_prompt_content = self.get_chat_prompt_for_chat(chat_id) + chat_prompt_block = f"{chat_prompt_content}\n" if chat_prompt_content else "" + if global_config.bot.qq_account == user_id and platform == global_config.bot.platform: return await global_prompt_manager.format_prompt( "private_replyer_self_prompt", @@ -738,6 +819,7 @@ class PrivateReplyer: reply_style=global_config.personality.reply_style, keywords_reaction_prompt=keywords_reaction_prompt, moderation_prompt=moderation_prompt_block, + chat_prompt=chat_prompt_block, ), selected_expressions else: return await global_prompt_manager.format_prompt( @@ -758,6 +840,7 @@ class PrivateReplyer: keywords_reaction_prompt=keywords_reaction_prompt, moderation_prompt=moderation_prompt_block, sender_name=sender, + chat_prompt=chat_prompt_block, ), selected_expressions async def build_prompt_rewrite_context( diff --git a/src/chat/replyer/prompt/replyer_prompt.py b/src/chat/replyer/prompt/replyer_prompt.py index 4e9b015d..26b47cb4 100644 --- a/src/chat/replyer/prompt/replyer_prompt.py +++ b/src/chat/replyer/prompt/replyer_prompt.py @@ -21,7 +21,7 @@ def init_replyer_prompt(): {reply_target_block}。 {identity} -你正在群里聊天,现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些,{mood_state} +{chat_prompt}你正在群里聊天,现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些,{mood_state} 尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。 {reply_style} 请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出一句回复内容就好。 @@ -41,7 +41,7 @@ def init_replyer_prompt(): {reply_target_block}。 {identity} -你正在和{sender_name}聊天,现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些,{mood_state} +{chat_prompt}你正在和{sender_name}聊天,现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些,{mood_state} 尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。 {reply_style} 请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。 @@ -61,7 +61,7 @@ def init_replyer_prompt(): 你现在想补充说明你刚刚自己的发言内容:{target},原因是{reason} 请你根据聊天内容,组织一条新回复。注意,{target} 是刚刚你自己的发言,你要在这基础上进一步发言,请按照你自己的角度来继续进行回复。注意保持上下文的连贯性。{mood_state} {identity} -尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。 +{chat_prompt}尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。 {reply_style} 请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。 {moderation_prompt}不要输出多余内容(包括冒号和引号,括号,表情包,at或 @等 )。 diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 8c29d066..57a3e232 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -656,6 +656,25 @@ class ExperimentalConfig(ConfigBase): enable_friend_chat: bool = False """是否启用好友聊天""" + chat_prompts: list[str] = field(default_factory=lambda: []) + """ + 为指定聊天添加额外的prompt配置列表 + 格式: ["platform:id:type:prompt内容", ...] + + 示例: + [ + "qq:114514:group:这是一个摄影群,你精通摄影知识", + "qq:19198:group:这是一个二次元交流群", + "qq:114514:private:这是你与好朋友的私聊" + ] + + 说明: + - platform: 平台名称,如 "qq" + - id: 群ID或用户ID + - type: "group" 或 "private" + - prompt内容: 要添加的额外prompt文本 + """ + @dataclass class MaimMessageConfig(ConfigBase): diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index d2621a35..20a4eea2 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "6.19.2" +version = "6.20.0" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请递增version的值 @@ -241,6 +241,16 @@ enable = true [experimental] #实验性功能 none = false # 暂无 +# 为指定聊天添加额外的prompt配置 +# 格式: ["platform:id:type:prompt内容", ...] +# 示例: +# chat_prompts = [ +# "qq:114514:group:这是一个摄影群,你精通摄影知识", +# "qq:19198:group:这是一个二次元交流群", +# "qq:114514:private:这是你与好朋友的私聊" +# ] +chat_prompts = [] + #此系统暂时移除,无效配置 [relationship]