diff --git a/changelogs/changelog.md b/changelogs/changelog.md index 4d9760629..0fb83b86e 100644 --- a/changelogs/changelog.md +++ b/changelogs/changelog.md @@ -9,7 +9,6 @@ 优化和修复: -- - 优化no_reply逻辑 - 优化Log显示 - 优化关系配置 diff --git a/scripts/interest_value_analysis.py b/scripts/interest_value_analysis.py index 9a624df34..2f56ae5d6 100644 --- a/scripts/interest_value_analysis.py +++ b/scripts/interest_value_analysis.py @@ -52,7 +52,7 @@ def calculate_interest_value_distribution(messages) -> Dict[str, int]: } for msg in messages: - if msg.interest_value is None: + if msg.interest_value is None or msg.interest_value == 0.0: continue value = float(msg.interest_value) @@ -80,7 +80,7 @@ def calculate_interest_value_distribution(messages) -> Dict[str, int]: def get_interest_value_stats(messages) -> Dict[str, float]: """Calculate basic statistics for interest_value""" - values = [float(msg.interest_value) for msg in messages if msg.interest_value is not None] + values = [float(msg.interest_value) for msg in messages if msg.interest_value is not None and msg.interest_value != 0.0] if not values: return { @@ -112,7 +112,8 @@ def get_available_chats() -> List[Tuple[str, str, int]]: chat_id = msg.chat_id count = Messages.select().where( (Messages.chat_id == chat_id) & - (Messages.interest_value.is_null(False)) + (Messages.interest_value.is_null(False)) & + (Messages.interest_value != 0.0) ).count() if count > 0: chat_counts[chat_id] = count @@ -174,7 +175,10 @@ def analyze_interest_values(chat_id: Optional[str] = None, start_time: Optional[ """Analyze interest values with optional filters""" # 构建查询条件 - query = Messages.select().where(Messages.interest_value.is_null(False)) + query = Messages.select().where( + (Messages.interest_value.is_null(False)) & + (Messages.interest_value != 0.0) + ) if chat_id: query = query.where(Messages.chat_id == chat_id) @@ -212,7 +216,7 @@ def analyze_interest_values(chat_id: Optional[str] = None, start_time: Optional[ print("时间范围: 不限制") print(f"\n基本统计:") - print(f"有效消息数量: {stats['count']}") + print(f"有效消息数量: {stats['count']} (排除null和0值)") print(f"最小值: {stats['min']:.3f}") print(f"最大值: {stats['max']:.3f}") print(f"平均值: {stats['avg']:.3f}") diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 68e896fab..5d203218c 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -30,9 +30,6 @@ from src.plugin_system.base.component_types import ActionInfo logger = get_logger("replyer") -ENABLE_S2S_MODE = True - - def init_prompt(): Prompt("你正在qq群里聊天,下面是群里在聊的内容:", "chat_target_group1") Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1") @@ -97,29 +94,29 @@ def init_prompt(): {relation_info_block} {extra_info_block} -你是一个AI虚拟主播,正在直播QQ聊天,同时也在直播间回复弹幕,不过回复的时候不用过多提及这点 {identity} {action_descriptions} -你现在的主要任务是和 {sender_name} 聊天。同时,也有其他用户会参与你们的聊天,你可以参考他们的回复内容,但是你主要还是关注你和{sender_name}的聊天内容。你现在的心情是:{mood_state} +你现在的主要任务是和 {sender_name} 聊天。同时,也有其他用户会参与你们的聊天,你可以参考他们的回复内容,但是你主要还是关注你和{sender_name}的聊天内容。 {background_dialogue_prompt} -------------------------------- {time_block} 这是你和{sender_name}的对话,你们正在交流中: + {core_dialogue_prompt} {reply_target_block} 对方最新发送的内容:{message_txt} -回复可以简短一些。可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,平淡一些。 -{config_expression_style}。注意不要复读你说过的话 +你现在的心情是:{mood_state} +{config_expression_style} +注意不要复读你说过的话 {keywords_reaction_prompt} 请注意不要输出多余内容(包括前后缀,冒号和引号,at或 @等 )。只输出回复内容。 {moderation_prompt} -不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。只输出回复内容,现在{sender_name}正在等待你的回复。 -你的回复风格不要浮夸,有逻辑和条理,请你继续回复{sender_name}。 -你的发言: +不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。只输出一条回复内容就好 +现在,你说: """, "s4u_style_prompt", ) @@ -132,7 +129,6 @@ class DefaultReplyer: model_configs: Optional[List[Dict[str, Any]]] = None, request_type: str = "focus.replyer", ): - self.log_prefix = "replyer" self.request_type = request_type if model_configs: @@ -196,7 +192,7 @@ class DefaultReplyer: } for key, value in reply_data.items(): if not value: - logger.debug(f"{self.log_prefix} 回复数据跳过{key},生成回复时将忽略。") + logger.debug(f"回复数据跳过{key},生成回复时将忽略。") # 3. 构建 Prompt with Timer("构建Prompt", {}): # 内部计时器,可选保留 @@ -217,7 +213,7 @@ class DefaultReplyer: # 加权随机选择一个模型配置 selected_model_config = self._select_weighted_model_config() logger.info( - f"{self.log_prefix} 使用模型配置: {selected_model_config.get('name', 'N/A')} (权重: {selected_model_config.get('weight', 1.0)})" + f"使用模型生成回复: {selected_model_config.get('name', 'N/A')} (选中概率: {selected_model_config.get('weight', 1.0)})" ) express_model = LLMRequest( @@ -226,9 +222,9 @@ class DefaultReplyer: ) if global_config.debug.show_prompt: - logger.info(f"{self.log_prefix}\n{prompt}\n") + logger.info(f"\n{prompt}\n") else: - logger.debug(f"{self.log_prefix}\n{prompt}\n") + logger.debug(f"\n{prompt}\n") content, (reasoning_content, model_name) = await express_model.generate_response_async(prompt) @@ -236,13 +232,13 @@ class DefaultReplyer: except Exception as llm_e: # 精简报错信息 - logger.error(f"{self.log_prefix}LLM 生成失败: {llm_e}") + logger.error(f"LLM 生成失败: {llm_e}") return False, None, prompt # LLM 调用失败则无法生成回复 return True, content, prompt except Exception as e: - logger.error(f"{self.log_prefix}回复生成意外失败: {e}") + logger.error(f"回复生成意外失败: {e}") traceback.print_exc() return False, None, prompt @@ -273,7 +269,7 @@ class DefaultReplyer: reasoning_content = None model_name = "unknown_model" if not prompt: - logger.error(f"{self.log_prefix}Prompt 构建失败,无法生成回复。") + logger.error(f"Prompt 构建失败,无法生成回复。") return False, None try: @@ -281,7 +277,7 @@ class DefaultReplyer: # 加权随机选择一个模型配置 selected_model_config = self._select_weighted_model_config() logger.info( - f"{self.log_prefix} 使用模型配置进行重写: {selected_model_config.get('name', 'N/A')} (权重: {selected_model_config.get('weight', 1.0)})" + f"使用模型重写回复: {selected_model_config.get('name', 'N/A')} (选中概率: {selected_model_config.get('weight', 1.0)})" ) express_model = LLMRequest( @@ -295,13 +291,13 @@ class DefaultReplyer: except Exception as llm_e: # 精简报错信息 - logger.error(f"{self.log_prefix}LLM 生成失败: {llm_e}") + logger.error(f"LLM 生成失败: {llm_e}") return False, None # LLM 调用失败则无法生成回复 return True, content except Exception as e: - logger.error(f"{self.log_prefix}回复生成意外失败: {e}") + logger.error(f"回复生成意外失败: {e}") traceback.print_exc() return False, None @@ -321,7 +317,7 @@ class DefaultReplyer: person_info_manager = get_person_info_manager() person_id = person_info_manager.get_person_id_by_person_name(sender) if not person_id: - logger.warning(f"{self.log_prefix} 未找到用户 {sender} 的ID,跳过信息提取") + logger.warning(f"未找到用户 {sender} 的ID,跳过信息提取") return f"你完全不认识{sender},不理解ta的相关信息。" return await relationship_fetcher.build_relation_info(person_id, points_num=5) @@ -340,7 +336,7 @@ class DefaultReplyer: ) if selected_expressions: - logger.debug(f"{self.log_prefix} 使用处理器选中的{len(selected_expressions)}个表达方式") + logger.debug(f"使用处理器选中的{len(selected_expressions)}个表达方式") for expr in selected_expressions: if isinstance(expr, dict) and "situation" in expr and "style" in expr: expr_type = expr.get("type", "style") @@ -349,7 +345,7 @@ class DefaultReplyer: else: style_habits.append(f"当{expr['situation']}时,使用 {expr['style']}") else: - logger.debug(f"{self.log_prefix} 没有从处理器获得表达方式,将使用空的表达方式") + logger.debug(f"没有从处理器获得表达方式,将使用空的表达方式") # 不再在replyer中进行随机选择,全部交给处理器处理 style_habits_str = "\n".join(style_habits) @@ -431,14 +427,14 @@ class DefaultReplyer: tool_info_str += f"- 【{tool_name}】{result_type}: {content}\n" tool_info_str += "以上是你获取到的实时信息,请在回复时参考这些信息。" - logger.info(f"{self.log_prefix} 获取到 {len(tool_results)} 个工具结果") + logger.info(f"获取到 {len(tool_results)} 个工具结果") return tool_info_str else: - logger.debug(f"{self.log_prefix} 未获取到任何工具结果") + logger.debug(f"未获取到任何工具结果") return "" except Exception as e: - logger.error(f"{self.log_prefix} 工具信息获取失败: {e}") + logger.error(f"工具信息获取失败: {e}") return "" def _parse_reply_target(self, target_message: str) -> tuple: @@ -630,31 +626,40 @@ class DefaultReplyer: # 并行执行四个构建任务 task_results = await asyncio.gather( self._time_and_run_task( - self.build_expression_habits(chat_talking_prompt_short, target), "build_expression_habits" + self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits" ), self._time_and_run_task( - self.build_relation_info(reply_data), "build_relation_info" + self.build_relation_info(reply_data), "relation_info" ), - self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "build_memory_block"), + self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"), self._time_and_run_task( - self.build_tool_info(chat_talking_prompt_short, reply_data, enable_tool=enable_tool), "build_tool_info" + self.build_tool_info(chat_talking_prompt_short, reply_data, enable_tool=enable_tool), "tool_info" ), ) + # 任务名称中英文映射 + task_name_mapping = { + "expression_habits": "选取表达方式", + "relation_info": "感受关系", + "memory_block": "回忆", + "tool_info": "使用工具" + } + # 处理结果 timing_logs = [] results_dict = {} for name, result, duration in task_results: results_dict[name] = result - timing_logs.append(f"{name}: {duration:.4f}s") + chinese_name = task_name_mapping.get(name, name) + timing_logs.append(f"{chinese_name}: {duration:.1f}s") if duration > 8: - logger.warning(f"回复生成前信息获取耗时过长: {name} 耗时: {duration:.4f}s,请使用更快的模型") - logger.info(f"回复生成前信息获取耗时: {'; '.join(timing_logs)}") + logger.warning(f"回复生成前信息获取耗时过长: {chinese_name} 耗时: {duration:.1f}s,请使用更快的模型") + logger.info(f"在回复前的步骤耗时: {'; '.join(timing_logs)}") - expression_habits_block = results_dict["build_expression_habits"] - relation_info = results_dict["build_relation_info"] - memory_block = results_dict["build_memory_block"] - tool_info = results_dict["build_tool_info"] + expression_habits_block = results_dict["expression_habits"] + relation_info = results_dict["relation_info"] + memory_block = results_dict["memory_block"] + tool_info = results_dict["tool_info"] keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target) diff --git a/src/config/config.py b/src/config/config.py index 8345a9f04..3b6f72a5f 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -31,6 +31,7 @@ from src.config.official_configs import ( LPMMKnowledgeConfig, RelationshipConfig, ToolConfig, + VoiceConfig, DebugConfig, CustomPromptConfig, ) @@ -328,7 +329,7 @@ class Config(ConfigBase): tool: ToolConfig debug: DebugConfig custom_prompt: CustomPromptConfig - + voice: VoiceConfig def load_config(config_path: str) -> Config: """ diff --git a/src/config/official_configs.py b/src/config/official_configs.py index be3ac1834..e55533053 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -106,9 +106,6 @@ class ChatConfig(ConfigBase): focus_value: float = 1.0 """麦麦的专注思考能力,越低越容易专注,消耗token也越多""" - enable_asr: bool = False - """是否启用语音识别""" - def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float: """ 根据当前时间和聊天流获取对应的 talk_frequency @@ -309,6 +306,13 @@ class ToolConfig(ConfigBase): enable_in_focus_chat: bool = True """是否在专注聊天中启用工具""" + +@dataclass +class VoiceConfig(ConfigBase): + """语音识别配置类""" + + enable_asr: bool = False + """是否启用语音识别""" @dataclass diff --git a/src/mais4u/mais4u_chat/s4u_prompt.py b/src/mais4u/mais4u_chat/s4u_prompt.py index 92a9ed277..d748c25e5 100644 --- a/src/mais4u/mais4u_chat/s4u_prompt.py +++ b/src/mais4u/mais4u_chat/s4u_prompt.py @@ -10,13 +10,13 @@ from datetime import datetime import asyncio from src.mais4u.s4u_config import s4u_config from src.chat.message_receive.message import MessageRecvS4U -from src.person_info.relationship_manager import get_relationship_manager +from src.person_info.relationship_fetcher import relationship_fetcher_manager +from src.person_info.person_info import PersonInfoManager, get_person_info_manager from src.chat.message_receive.chat_stream import ChatStream from src.mais4u.mais4u_chat.super_chat_manager import get_super_chat_manager from src.mais4u.mais4u_chat.screen_manager import screen_manager from src.chat.express.expression_selector import expression_selector from .s4u_mood_manager import mood_manager -from src.person_info.person_info import PersonInfoManager, get_person_info_manager from src.mais4u.mais4u_chat.internal_manager import internal_manager logger = get_logger("prompt") @@ -149,9 +149,17 @@ class PromptBuilder: relation_prompt = "" if global_config.relationship.enable_relationship and who_chat_in_group: - relationship_manager = get_relationship_manager() + relationship_fetcher = relationship_fetcher_manager.get_fetcher(chat_stream.stream_id) + + # 将 (platform, user_id, nickname) 转换为 person_id + person_ids = [] + for person in who_chat_in_group: + person_id = PersonInfoManager.get_person_id(person[0], person[1]) + person_ids.append(person_id) + + # 使用 RelationshipFetcher 的 build_relation_info 方法,设置 points_num=3 保持与原来相同的行为 relation_info_list = await asyncio.gather( - *[relationship_manager.build_relationship_info(person) for person in who_chat_in_group] + *[relationship_fetcher.build_relation_info(person_id, points_num=3) for person_id in person_ids] ) relation_info = "".join(relation_info_list) if relation_info: diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index eb463da35..6be0ad277 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -41,8 +41,6 @@ person_info_default = { "know_times": 0, "know_since": None, "last_know": None, - # "user_cardname": None, # This field is not in Peewee model PersonInfo - # "user_avatar": None, # This field is not in Peewee model PersonInfo "impression": None, # Corrected from person_impression "short_impression": None, "info_list": None, diff --git a/src/person_info/relationship_fetcher.py b/src/person_info/relationship_fetcher.py index deeb4c370..99f3be303 100644 --- a/src/person_info/relationship_fetcher.py +++ b/src/person_info/relationship_fetcher.py @@ -112,15 +112,6 @@ class RelationshipFetcher: current_points = await person_info_manager.get_value(person_id, "points") or [] - if isinstance(current_points, str): - try: - current_points = json.loads(current_points) - except json.JSONDecodeError: - logger.error(f"解析points JSON失败: {current_points}") - current_points = [] - elif not isinstance(current_points, list): - current_points = [] - # 按时间排序forgotten_points current_points.sort(key=lambda x: x[2]) # 按权重加权随机抽取最多3个不重复的points,point[1]的值在1-10之间,权重越高被抽到概率越大 @@ -370,60 +361,6 @@ class RelationshipFetcher: logger.error(f"{self.log_prefix} 执行信息提取时出错: {e}") logger.error(traceback.format_exc()) - def _organize_known_info(self) -> str: - """组织已知的用户信息为字符串 - - Returns: - str: 格式化的用户信息字符串 - """ - persons_infos_str = "" - - if self.info_fetched_cache: - persons_with_known_info = [] # 有已知信息的人员 - persons_with_unknown_info = [] # 有未知信息的人员 - - for person_id in self.info_fetched_cache: - person_known_infos = [] - person_unknown_infos = [] - person_name = "" - - for info_type in self.info_fetched_cache[person_id]: - person_name = self.info_fetched_cache[person_id][info_type]["person_name"] - if not self.info_fetched_cache[person_id][info_type]["unknown"]: - info_content = self.info_fetched_cache[person_id][info_type]["info"] - person_known_infos.append(f"[{info_type}]:{info_content}") - else: - person_unknown_infos.append(info_type) - - # 如果有已知信息,添加到已知信息列表 - if person_known_infos: - known_info_str = ";".join(person_known_infos) + ";" - persons_with_known_info.append((person_name, known_info_str)) - - # 如果有未知信息,添加到未知信息列表 - if person_unknown_infos: - persons_with_unknown_info.append((person_name, person_unknown_infos)) - - # 先输出有已知信息的人员 - for person_name, known_info_str in persons_with_known_info: - persons_infos_str += f"你对 {person_name} 的了解:{known_info_str}\n" - - # 统一处理未知信息,避免重复的警告文本 - if persons_with_unknown_info: - unknown_persons_details = [] - for person_name, unknown_types in persons_with_unknown_info: - unknown_types_str = "、".join(unknown_types) - unknown_persons_details.append(f"{person_name}的[{unknown_types_str}]") - - if len(unknown_persons_details) == 1: - persons_infos_str += ( - f"你不了解{unknown_persons_details[0]}信息,不要胡乱回答,可以直接说不知道或忘记了;\n" - ) - else: - unknown_all_str = "、".join(unknown_persons_details) - persons_infos_str += f"你不了解{unknown_all_str}等信息,不要胡乱回答,可以直接说不知道或忘记了;\n" - - return persons_infos_str async def _save_info_to_cache(self, person_id: str, info_type: str, info_content: str): # sourcery skip: use-next diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index ecce06c65..01cc89e9a 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -55,60 +55,6 @@ class RelationshipManager: # person_id=person_id, user_nickname=user_nickname, user_cardname=user_cardname, user_avatar=user_avatar # ) - async def build_relationship_info(self, person, is_id: bool = False) -> str: - if is_id: - person_id = person - else: - person_id = PersonInfoManager.get_person_id(person[0], person[1]) - person_info_manager = get_person_info_manager() - person_name = await person_info_manager.get_value(person_id, "person_name") - if not person_name or person_name == "none": - return "" - short_impression = await person_info_manager.get_value(person_id, "short_impression") - - current_points = await person_info_manager.get_value(person_id, "points") or [] - # print(f"current_points: {current_points}") - if isinstance(current_points, str): - try: - current_points = json.loads(current_points) - except json.JSONDecodeError: - logger.error(f"解析points JSON失败: {current_points}") - current_points = [] - elif not isinstance(current_points, list): - current_points = [] - - # 按时间排序forgotten_points - current_points.sort(key=lambda x: x[2]) - # 按权重加权随机抽取3个points,point[1]的值在1-10之间,权重越高被抽到概率越大 - if len(current_points) > 3: - # point[1] 取值范围1-10,直接作为权重 - weights = [max(1, min(10, int(point[1]))) for point in current_points] - points = random.choices(current_points, weights=weights, k=3) - else: - points = current_points - - # 构建points文本 - points_text = "\n".join([f"{point[2]}:{point[0]}" for point in points]) - - nickname_str = await person_info_manager.get_value(person_id, "nickname") - platform = await person_info_manager.get_value(person_id, "platform") - - if person_name == nickname_str and not short_impression: - return "" - - if person_name == nickname_str: - relation_prompt = f"'{person_name}' :" - else: - relation_prompt = f"'{person_name}' ,ta在{platform}上的昵称是{nickname_str}。" - - if short_impression: - relation_prompt += f"你对ta的印象是:{short_impression}。\n" - - if points_text: - relation_prompt += f"你记得ta最近做的事:{points_text}" - - return relation_prompt - async def update_person_impression(self, person_id, timestamp, bot_engaged_messages: List[Dict[str, Any]]): """更新用户印象 diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 0d11f3dea..8dfe2fc4c 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "4.4.6" +version = "4.4.7" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请在修改后将version的值进行变更 @@ -33,7 +33,7 @@ compress_identity = true # 是否压缩身份,压缩后会精简身份信息 # 表达方式 enable_expression = true # 是否启用表达方式 # 描述麦麦说话的表达风格,表达习惯,例如:(请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景。) -expression_style = "请回复的平淡些,简短一些,说中文,可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,不要刻意突出自身学科背景。" +expression_style = "回复可以简短一些。可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,平淡一些。" enable_expression_learning = false # 是否启用表达学习,麦麦会学习不同群里人类说话风格(群之间不互通) learning_interval = 350 # 学习间隔 单位秒 @@ -87,8 +87,6 @@ talk_frequency_adjust = [ # - 时间支持跨天,例如 "00:10,0.3" 表示从凌晨0:10开始使用频率0.3 # - 系统会自动将 "platform:id:type" 转换为内部的哈希chat_id进行匹配 -enable_asr = false # 是否启用语音识别,启用后麦麦可以通过语音输入进行对话,启用该功能需要配置语音识别模型[model.voice] - [message_receive] # 以下是消息过滤,可以根据规则过滤特定消息,将不会读取这些消息 ban_words = [ @@ -144,6 +142,9 @@ enable_instant_memory = false # 是否启用即时记忆,测试功能,可能 #不希望记忆的词,已经记忆的不会受到影响,需要手动清理 memory_ban_words = [ "表情包", "图片", "回复", "聊天记录" ] +[voice] +enable_asr = false # 是否启用语音识别,启用后麦麦可以识别语音消息,启用该功能需要配置语音识别模型[model.voice]s + [mood] enable_mood = true # 是否启用情绪系统 mood_update_interval = 1.0 # 情绪更新间隔 单位秒