From 0b2bf81f750aae4f2eb04b4ef83c649ca7d7be73 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Thu, 3 Jul 2025 12:24:38 +0800 Subject: [PATCH] =?UTF-8?q?remove=20&=20fix=EF=BC=9A=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E4=BA=BA=E6=A0=BC=E8=A1=A8=E8=BE=BE=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E8=AF=8D=E5=A4=B1=E6=95=88=EF=BC=8C=E7=A7=81?= =?UTF-8?q?=E8=81=8A=E5=BC=BA=E5=88=B6focus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- s4u.s4u1 | 0 src/api/main.py | 1 + src/chat/express/expression_selector.py | 25 +- src/chat/express/exprssion_learner.py | 31 +-- .../focus_chat/heartflow_message_processor.py | 50 +--- src/chat/heart_flow/sub_heartflow.py | 5 +- src/chat/heart_flow/subheartflow_manager.py | 6 - src/chat/message_receive/bot.py | 88 ++++--- src/chat/replyer/default_generator.py | 10 + src/config/official_configs.py | 3 + src/individuality/expression_style.py | 238 ------------------ src/individuality/individuality.py | 5 - src/main.py | 6 +- src/mais4u/mais4u_chat/s4u_chat.py | 58 +++-- src/mais4u/mais4u_chat/s4u_prompt.py | 1 - template/bot_config_template.toml | 3 +- 16 files changed, 140 insertions(+), 390 deletions(-) create mode 100644 s4u.s4u1 delete mode 100644 src/individuality/expression_style.py diff --git a/s4u.s4u1 b/s4u.s4u1 new file mode 100644 index 000000000..e69de29bb diff --git a/src/api/main.py b/src/api/main.py index 81cd5a24a..598b8aec5 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -109,3 +109,4 @@ async def get_system_basic_info(): def start_api_server(): """启动API服务器""" get_global_server().register_router(router, prefix="/api/v1") + # pass diff --git a/src/chat/express/expression_selector.py b/src/chat/express/expression_selector.py index ca63db943..8c60f1d6d 100644 --- a/src/chat/express/expression_selector.py +++ b/src/chat/express/expression_selector.py @@ -80,14 +80,16 @@ class ExpressionSelector: ) def get_random_expressions( - self, chat_id: str, style_num: int, grammar_num: int, personality_num: int + self, chat_id: str, total_num: int, style_percentage: float, grammar_percentage: float ) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]: ( learnt_style_expressions, learnt_grammar_expressions, - personality_expressions, ) = self.expression_learner.get_expression_by_chat_id(chat_id) + style_num = int(total_num * style_percentage) + grammar_num = int(total_num * grammar_percentage) + # 按权重抽样(使用count作为权重) if learnt_style_expressions: style_weights = [expr.get("count", 1) for expr in learnt_style_expressions] @@ -101,13 +103,7 @@ class ExpressionSelector: else: selected_grammar = [] - if personality_expressions: - personality_weights = [expr.get("count", 1) for expr in personality_expressions] - selected_personality = weighted_sample(personality_expressions, personality_weights, personality_num) - else: - selected_personality = [] - - return selected_style, selected_grammar, selected_personality + return selected_style, selected_grammar def update_expressions_count_batch(self, expressions_to_update: List[Dict[str, str]], increment: float = 0.1): """对一批表达方式更新count值,按文件分组后一次性写入""" @@ -174,7 +170,7 @@ class ExpressionSelector: """使用LLM选择适合的表达方式""" # 1. 获取35个随机表达方式(现在按权重抽取) - style_exprs, grammar_exprs, personality_exprs = self.get_random_expressions(chat_id, 25, 25, 10) + style_exprs, grammar_exprs= self.get_random_expressions(chat_id, 50, 0.5, 0.5) # 2. 构建所有表达方式的索引和情境列表 all_expressions = [] @@ -196,13 +192,6 @@ class ExpressionSelector: all_expressions.append(expr_with_type) all_situations.append(f"{len(all_expressions)}.{expr['situation']}") - # 添加personality表达方式 - for expr in personality_exprs: - if isinstance(expr, dict) and "situation" in expr and "style" in expr: - expr_with_type = expr.copy() - expr_with_type["type"] = "style_personality" - all_expressions.append(expr_with_type) - all_situations.append(f"{len(all_expressions)}.{expr['situation']}") if not all_expressions: logger.warning("没有找到可用的表达方式") @@ -260,7 +249,7 @@ class ExpressionSelector: # 对选中的所有表达方式,一次性更新count数 if valid_expressions: - self.update_expressions_count_batch(valid_expressions, 0.003) + self.update_expressions_count_batch(valid_expressions, 0.006) # logger.info(f"LLM从{len(all_expressions)}个情境中选择了{len(valid_expressions)}个") return valid_expressions diff --git a/src/chat/express/exprssion_learner.py b/src/chat/express/exprssion_learner.py index a18961ef1..c8417bb94 100644 --- a/src/chat/express/exprssion_learner.py +++ b/src/chat/express/exprssion_learner.py @@ -76,14 +76,13 @@ class ExpressionLearner: def get_expression_by_chat_id( self, chat_id: str - ) -> Tuple[List[Dict[str, str]], List[Dict[str, str]], List[Dict[str, str]]]: + ) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]: """ - 获取指定chat_id的style和grammar表达方式, 同时获取全局的personality表达方式 + 获取指定chat_id的style和grammar表达方式 返回的每个表达方式字典中都包含了source_id, 用于后续的更新操作 """ learnt_style_expressions = [] learnt_grammar_expressions = [] - personality_expressions = [] # 获取style表达方式 style_dir = os.path.join("data", "expression", "learnt_style", str(chat_id)) @@ -111,19 +110,8 @@ class ExpressionLearner: except Exception as e: logger.error(f"读取grammar表达方式失败: {e}") - # 获取personality表达方式 - personality_file = os.path.join("data", "expression", "personality", "expressions.json") - if os.path.exists(personality_file): - try: - with open(personality_file, "r", encoding="utf-8") as f: - expressions = json.load(f) - for expr in expressions: - expr["source_id"] = "personality" # 添加来源ID - personality_expressions.append(expr) - except Exception as e: - logger.error(f"读取personality表达方式失败: {e}") - return learnt_style_expressions, learnt_grammar_expressions, personality_expressions + return learnt_style_expressions, learnt_grammar_expressions def is_similar(self, s1: str, s2: str) -> bool: """ @@ -428,11 +416,12 @@ class ExpressionLearner: init_prompt() -expression_learner = None +if global_config.expression.enable_expression: + expression_learner = None -def get_expression_learner(): - global expression_learner - if expression_learner is None: - expression_learner = ExpressionLearner() - return expression_learner + def get_expression_learner(): + global expression_learner + if expression_learner is None: + expression_learner = ExpressionLearner() + return expression_learner diff --git a/src/chat/focus_chat/heartflow_message_processor.py b/src/chat/focus_chat/heartflow_message_processor.py index d7299d4c6..691bb59c1 100644 --- a/src/chat/focus_chat/heartflow_message_processor.py +++ b/src/chat/focus_chat/heartflow_message_processor.py @@ -3,16 +3,14 @@ from src.config.config import global_config from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.storage import MessageStorage from src.chat.heart_flow.heartflow import heartflow -from src.chat.message_receive.chat_stream import get_chat_manager, ChatStream +from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.utils.utils import is_mentioned_bot_in_message from src.chat.utils.timer_calculator import Timer from src.common.logger import get_logger - -import math import re +import math import traceback from typing import Optional, Tuple -from maim_message import UserInfo from src.person_info.relationship_manager import get_relationship_manager @@ -90,44 +88,7 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool]: return interested_rate, is_mentioned -def _check_ban_words(text: str, chat: ChatStream, userinfo: UserInfo) -> bool: - """检查消息是否包含过滤词 - Args: - text: 待检查的文本 - chat: 聊天对象 - userinfo: 用户信息 - - Returns: - bool: 是否包含过滤词 - """ - for word in global_config.message_receive.ban_words: - if word in text: - chat_name = chat.group_info.group_name if chat.group_info else "私聊" - logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}") - logger.info(f"[过滤词识别]消息中含有{word},filtered") - return True - return False - - -def _check_ban_regex(text: str, chat: ChatStream, userinfo: UserInfo) -> bool: - """检查消息是否匹配过滤正则表达式 - - Args: - text: 待检查的文本 - chat: 聊天对象 - userinfo: 用户信息 - - Returns: - bool: 是否匹配过滤正则 - """ - for pattern in global_config.message_receive.ban_msgs_regex: - if re.search(pattern, text): - chat_name = chat.group_info.group_name if chat.group_info else "私聊" - logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}") - logger.info(f"[正则表达式过滤]消息匹配到{pattern},filtered") - return True - return False class HeartFCMessageReceiver: @@ -167,12 +128,6 @@ class HeartFCMessageReceiver: subheartflow = await heartflow.get_or_create_subheartflow(chat.stream_id) message.update_chat_stream(chat) - # 3. 过滤检查 - if _check_ban_words(message.processed_plain_text, chat, userinfo) or _check_ban_regex( - message.raw_message, chat, userinfo - ): - return - # 6. 兴趣度计算与更新 interested_rate, is_mentioned = await _calculate_interest(message) subheartflow.add_message_to_normal_chat_cache(message, interested_rate, is_mentioned) @@ -183,7 +138,6 @@ class HeartFCMessageReceiver: current_talk_frequency = global_config.chat.get_current_talk_frequency(chat.stream_id) # 如果消息中包含图片标识,则日志展示为图片 - import re picid_match = re.search(r"\[picid:([^\]]+)\]", message.processed_plain_text) if picid_match: diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index d602ea3a8..03bb71c62 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -62,7 +62,10 @@ class SubHeartflow: """异步初始化方法,创建兴趣流并确定聊天类型""" # 根据配置决定初始状态 - if global_config.chat.chat_mode == "focus": + if not self.is_group_chat: + logger.debug(f"{self.log_prefix} 检测到是私聊,将直接尝试进入 FOCUSED 状态。") + await self.change_chat_state(ChatState.FOCUSED) + elif global_config.chat.chat_mode == "focus": logger.debug(f"{self.log_prefix} 配置为 focus 模式,将直接尝试进入 FOCUSED 状态。") await self.change_chat_state(ChatState.FOCUSED) else: # "auto" 或其他模式保持原有逻辑或默认为 NORMAL diff --git a/src/chat/heart_flow/subheartflow_manager.py b/src/chat/heart_flow/subheartflow_manager.py index faaac5ceb..587234cba 100644 --- a/src/chat/heart_flow/subheartflow_manager.py +++ b/src/chat/heart_flow/subheartflow_manager.py @@ -91,16 +91,10 @@ class SubHeartflowManager: return subflow try: - # 初始化子心流, 传入 mai_state_info new_subflow = SubHeartflow( subheartflow_id, ) - # 首先创建并添加聊天观察者 - # observation = ChattingObservation(chat_id=subheartflow_id) - # await observation.initialize() - # new_subflow.add_observation(observation) - # 然后再进行异步初始化,此时 SubHeartflow 内部若需启动 HeartFChatting,就能拿到 observation await new_subflow.initialize() diff --git a/src/chat/message_receive/bot.py b/src/chat/message_receive/bot.py index 999614b3d..e7e503aad 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -15,6 +15,9 @@ from src.config.config import global_config from src.plugin_system.core.component_registry import component_registry # 导入新插件系统 from src.plugin_system.base.base_command import BaseCommand from src.mais4u.mais4u_chat.s4u_msg_processor import S4UMessageProcessor +from maim_message import UserInfo +from src.chat.message_receive.chat_stream import ChatStream +import re # 定义日志配置 # 获取项目根目录(假设本文件在src/chat/message_receive/下,根目录为上上上级目录) @@ -29,6 +32,44 @@ if ENABLE_S4U_CHAT: # 配置主程序日志格式 logger = get_logger("chat") +def _check_ban_words(text: str, chat: ChatStream, userinfo: UserInfo) -> bool: + """检查消息是否包含过滤词 + + Args: + text: 待检查的文本 + chat: 聊天对象 + userinfo: 用户信息 + + Returns: + bool: 是否包含过滤词 + """ + for word in global_config.message_receive.ban_words: + if word in text: + chat_name = chat.group_info.group_name if chat.group_info else "私聊" + logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}") + logger.info(f"[过滤词识别]消息中含有{word},filtered") + return True + return False + + +def _check_ban_regex(text: str, chat: ChatStream, userinfo: UserInfo) -> bool: + """检查消息是否匹配过滤正则表达式 + + Args: + text: 待检查的文本 + chat: 聊天对象 + userinfo: 用户信息 + + Returns: + bool: 是否匹配过滤正则 + """ + for pattern in global_config.message_receive.ban_msgs_regex: + if re.search(pattern, text): + chat_name = chat.group_info.group_name if chat.group_info else "私聊" + logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}") + logger.info(f"[正则表达式过滤]消息匹配到{pattern},filtered") + return True + return False class ChatBot: def __init__(self): @@ -49,17 +90,6 @@ class ChatBot: self._started = True - async def _create_pfc_chat(self, message: MessageRecv): - try: - if global_config.experimental.pfc_chatting: - chat_id = str(message.chat_stream.stream_id) - private_name = str(message.message_info.user_info.user_nickname) - - await self.pfc_manager.get_or_create_conversation(chat_id, private_name) - - except Exception as e: - logger.error(f"创建PFC聊天失败: {e}") - async def _process_commands_with_new_system(self, message: MessageRecv): # sourcery skip: use-named-expression """使用新插件系统处理命令""" @@ -149,14 +179,20 @@ class ChatBot: return get_chat_manager().register_message(message) - - # 创建聊天流 + chat = await get_chat_manager().get_or_create_stream( platform=message.message_info.platform, user_info=user_info, group_info=group_info, ) + message.update_chat_stream(chat) + + # 过滤检查 + if _check_ban_words(message.processed_plain_text, chat, user_info) or _check_ban_regex( + message.raw_message, chat, user_info + ): + return # 处理消息内容,生成纯文本 await message.process() @@ -183,26 +219,12 @@ class ChatBot: template_group_name = None async def preprocess(): - logger.debug("开始预处理消息...") - # 如果在私聊中 - if group_info is None: - logger.debug("检测到私聊消息") - if ENABLE_S4U_CHAT: - logger.debug("进入S4U私聊处理流程") - await self.s4u_message_processor.process_message(message) - return - else: - logger.debug("进入普通心流私聊处理") - await self.heartflow_message_receiver.process_message(message) - # 群聊默认进入心流消息处理逻辑 - else: - if ENABLE_S4U_CHAT: - logger.debug("进入S4U私聊处理流程") - await self.s4u_message_processor.process_message(message) - return - else: - logger.debug(f"检测到群聊消息,群ID: {group_info.group_id}") - await self.heartflow_message_receiver.process_message(message) + if ENABLE_S4U_CHAT: + logger.info("进入S4U流程") + await self.s4u_message_processor.process_message(message) + return + + await self.heartflow_message_receiver.process_message(message) if template_group_name: async with global_prompt_manager.async_message_scope(template_group_name): diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 96d1390a8..1bce319ca 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -336,6 +336,9 @@ class DefaultReplyer: return False, None async def build_relation_info(self, reply_data=None, chat_history=None): + if not global_config.relationship.enable_relationship: + return "" + relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id) if not reply_data: return "" @@ -355,6 +358,9 @@ class DefaultReplyer: return relation_info async def build_expression_habits(self, chat_history, target): + if not global_config.expression.enable_expression: + return "" + style_habbits = [] grammar_habbits = [] @@ -390,6 +396,9 @@ class DefaultReplyer: return expression_habits_block async def build_memory_block(self, chat_history, target): + if not global_config.memory.enable_memory: + return "" + running_memorys = await self.memory_activator.activate_memory_with_chat_history( target_message=target, chat_history_prompt=chat_history ) @@ -415,6 +424,7 @@ class DefaultReplyer: Returns: str: 工具信息字符串 """ + if not reply_data: return "" diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 35248e7e7..1ff1ae768 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -322,6 +322,9 @@ class FocusChatConfig(ConfigBase): class ExpressionConfig(ConfigBase): """表达配置类""" + enable_expression: bool = True + """是否启用表达方式""" + expression_style: str = "" """表达风格""" diff --git a/src/individuality/expression_style.py b/src/individuality/expression_style.py deleted file mode 100644 index 74f05bbbf..000000000 --- a/src/individuality/expression_style.py +++ /dev/null @@ -1,238 +0,0 @@ -import random - -from src.common.logger import get_logger -from src.llm_models.utils_model import LLMRequest -from src.config.config import global_config -from src.chat.utils.prompt_builder import Prompt, global_prompt_manager -from typing import List, Tuple -import os -import json -from datetime import datetime - -logger = get_logger("expressor") - - -def init_prompt() -> None: - personality_expression_prompt = """ -你的人物设定:{personality} - -你说话的表达方式:{expression_style} - -请从以上表达方式中总结出这个角色可能的语言风格,你必须严格根据人设引申,不要输出例子 -思考回复的特殊内容和情感 -思考有没有特殊的梗,一并总结成语言风格 -总结成如下格式的规律,总结的内容要详细,但具有概括性: -当"xxx"时,可以"xxx", xxx不超过10个字 - -例如(不要输出例子): -当"表示十分惊叹"时,使用"我嘞个xxxx" -当"表示讽刺的赞同,不想讲道理"时,使用"对对对" -当"想说明某个观点,但懒得明说",使用"懂的都懂" - -现在请你概括 -""" - Prompt(personality_expression_prompt, "personality_expression_prompt") - - -class PersonalityExpression: - def __init__(self): - self.express_learn_model: LLMRequest = LLMRequest( - model=global_config.model.replyer_1, - max_tokens=512, - request_type="expressor.learner", - ) - self.meta_file_path = os.path.join("data", "expression", "personality", "expression_style_meta.json") - self.expressions_file_path = os.path.join("data", "expression", "personality", "expressions.json") - self.max_calculations = 20 - - def _read_meta_data(self): - if os.path.exists(self.meta_file_path): - try: - with open(self.meta_file_path, "r", encoding="utf-8") as meta_file: - meta_data = json.load(meta_file) - # 检查是否有last_update_time字段 - if "last_update_time" not in meta_data: - logger.warning(f"{self.meta_file_path} 中缺少last_update_time字段,将重新开始。") - # 清空并重写元数据文件 - self._write_meta_data({"last_style_text": None, "count": 0, "last_update_time": None}) - # 清空并重写表达文件 - if os.path.exists(self.expressions_file_path): - with open(self.expressions_file_path, "w", encoding="utf-8") as expressions_file: - json.dump([], expressions_file, ensure_ascii=False, indent=2) - logger.debug(f"已清空表达文件: {self.expressions_file_path}") - return {"last_style_text": None, "count": 0, "last_update_time": None} - return meta_data - except json.JSONDecodeError: - logger.warning(f"无法解析 {self.meta_file_path} 中的JSON数据,将重新开始。") - # 清空并重写元数据文件 - self._write_meta_data({"last_style_text": None, "count": 0, "last_update_time": None}) - # 清空并重写表达文件 - if os.path.exists(self.expressions_file_path): - with open(self.expressions_file_path, "w", encoding="utf-8") as expressions_file: - json.dump([], expressions_file, ensure_ascii=False, indent=2) - logger.debug(f"已清空表达文件: {self.expressions_file_path}") - return {"last_style_text": None, "count": 0, "last_update_time": None} - return {"last_style_text": None, "count": 0, "last_update_time": None} - - def _write_meta_data(self, data): - os.makedirs(os.path.dirname(self.meta_file_path), exist_ok=True) - with open(self.meta_file_path, "w", encoding="utf-8") as meta_file: - json.dump(data, meta_file, ensure_ascii=False, indent=2) - - async def extract_and_store_personality_expressions(self): - """ - 检查data/expression/personality目录,不存在则创建。 - 用peronality变量作为chat_str,调用LLM生成表达风格,解析后count=100,存储到expressions.json。 - 如果expression_style、personality或identity发生变化,则删除旧的expressions.json并重置计数。 - 对于相同的expression_style,最多计算self.max_calculations次。 - """ - os.makedirs(os.path.dirname(self.expressions_file_path), exist_ok=True) - - current_style_text = global_config.expression.expression_style - current_personality = global_config.personality.personality_core - - meta_data = self._read_meta_data() - - last_style_text = meta_data.get("last_style_text") - last_personality = meta_data.get("last_personality") - count = meta_data.get("count", 0) - - # 检查是否有任何变化 - if current_style_text != last_style_text or current_personality != last_personality: - logger.info( - f"检测到变化:\n风格: '{last_style_text}' -> '{current_style_text}'\n人格: '{last_personality}' -> '{current_personality}'" - ) - count = 0 - if os.path.exists(self.expressions_file_path): - try: - os.remove(self.expressions_file_path) - logger.info(f"已删除旧的表达文件: {self.expressions_file_path}") - except OSError as e: - logger.error(f"删除旧的表达文件 {self.expressions_file_path} 失败: {e}") - - if count >= self.max_calculations: - logger.debug(f"对于当前配置已达到最大计算次数 ({self.max_calculations})。跳过提取。") - # 即使跳过,也更新元数据以反映当前配置已被识别且计数已满 - self._write_meta_data( - { - "last_style_text": current_style_text, - "last_personality": current_personality, - "count": count, - "last_update_time": meta_data.get("last_update_time"), - } - ) - return - - # 构建prompt - prompt = await global_prompt_manager.format_prompt( - "personality_expression_prompt", - personality=current_personality, - expression_style=current_style_text, - ) - - try: - response, _ = await self.express_learn_model.generate_response_async(prompt) - except Exception as e: - logger.error(f"个性表达方式提取失败: {e}") - # 如果提取失败,保存当前的配置和未增加的计数 - self._write_meta_data( - { - "last_style_text": current_style_text, - "last_personality": current_personality, - "count": count, - "last_update_time": meta_data.get("last_update_time"), - } - ) - return - - logger.info(f"个性表达方式提取response: {response}") - - # 转为dict并count=100 - if response != "": - expressions = self.parse_expression_response(response, "personality") - # 读取已有的表达方式 - existing_expressions = [] - if os.path.exists(self.expressions_file_path): - try: - with open(self.expressions_file_path, "r", encoding="utf-8") as f: - existing_expressions = json.load(f) - except (json.JSONDecodeError, FileNotFoundError): - logger.warning(f"无法读取或解析 {self.expressions_file_path},将创建新的表达文件。") - - # 创建新的表达方式 - new_expressions = [] - for _, situation, style in expressions: - new_expressions.append({"situation": situation, "style": style, "count": 1}) - - # 合并表达方式,如果situation和style相同则累加count - merged_expressions = existing_expressions.copy() - for new_expr in new_expressions: - found = False - for existing_expr in merged_expressions: - if ( - existing_expr["situation"] == new_expr["situation"] - and existing_expr["style"] == new_expr["style"] - ): - existing_expr["count"] += new_expr["count"] - found = True - break - if not found: - merged_expressions.append(new_expr) - - # 超过50条时随机删除多余的,只保留50条 - if len(merged_expressions) > 50: - remove_count = len(merged_expressions) - 50 - remove_indices = set(random.sample(range(len(merged_expressions)), remove_count)) - merged_expressions = [item for idx, item in enumerate(merged_expressions) if idx not in remove_indices] - - with open(self.expressions_file_path, "w", encoding="utf-8") as f: - json.dump(merged_expressions, f, ensure_ascii=False, indent=2) - logger.info(f"已写入{len(merged_expressions)}条表达到{self.expressions_file_path}") - - # 成功提取后更新元数据 - count += 1 - current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self._write_meta_data( - { - "last_style_text": current_style_text, - "last_personality": current_personality, - "count": count, - "last_update_time": current_time, - } - ) - logger.info(f"成功处理。当前配置的计数现在是 {count},最后更新时间:{current_time}。") - else: - logger.warning(f"个性表达方式提取失败,模型返回空内容: {response}") - - def parse_expression_response(self, response: str, chat_id: str) -> List[Tuple[str, str, str]]: - """ - 解析LLM返回的表达风格总结,每一行提取"当"和"使用"之间的内容,存储为(situation, style)元组 - """ - expressions: List[Tuple[str, str, str]] = [] - for line in response.splitlines(): - line = line.strip() - if not line: - continue - # 查找"当"和下一个引号 - idx_when = line.find('当"') - if idx_when == -1: - continue - idx_quote1 = idx_when + 1 - idx_quote2 = line.find('"', idx_quote1 + 1) - if idx_quote2 == -1: - continue - situation = line[idx_quote1 + 1 : idx_quote2] - # 查找"使用" - idx_use = line.find('使用"', idx_quote2) - if idx_use == -1: - continue - idx_quote3 = idx_use + 2 - idx_quote4 = line.find('"', idx_quote3 + 1) - if idx_quote4 == -1: - continue - style = line[idx_quote3 + 1 : idx_quote4] - expressions.append((chat_id, situation, style)) - return expressions - - -init_prompt() diff --git a/src/individuality/individuality.py b/src/individuality/individuality.py index 6f2509cfe..8365c0888 100644 --- a/src/individuality/individuality.py +++ b/src/individuality/individuality.py @@ -1,11 +1,9 @@ from typing import Optional -import asyncio import ast from src.llm_models.utils_model import LLMRequest from .personality import Personality from .identity import Identity -from .expression_style import PersonalityExpression import random import json import os @@ -27,7 +25,6 @@ class Individuality: # 正常初始化实例属性 self.personality: Optional[Personality] = None self.identity: Optional[Identity] = None - self.express_style: PersonalityExpression = PersonalityExpression() self.name = "" self.bot_person_id = "" @@ -151,8 +148,6 @@ class Individuality: else: logger.error("人设构建失败") - asyncio.create_task(self.express_style.extract_and_store_personality_expressions()) - def to_dict(self) -> dict: """将个体特征转换为字典格式""" return { diff --git a/src/main.py b/src/main.py index 02ad56e6a..fbfc778bc 100644 --- a/src/main.py +++ b/src/main.py @@ -19,7 +19,7 @@ from src.common.logger import get_logger from src.individuality.individuality import get_individuality, Individuality from src.common.server import get_global_server, Server from rich.traceback import install -from src.api.main import start_api_server +# from src.api.main import start_api_server # 导入新的插件管理器 from src.plugin_system.core.plugin_manager import plugin_manager @@ -85,8 +85,8 @@ class MainSystem: await async_task_manager.add_task(TelemetryHeartBeatTask()) # 启动API服务器 - start_api_server() - logger.info("API服务器启动成功") + # start_api_server() + # logger.info("API服务器启动成功") # 加载所有actions,包括默认的和插件的 plugin_count, component_count = plugin_manager.load_all_plugins() diff --git a/src/mais4u/mais4u_chat/s4u_chat.py b/src/mais4u/mais4u_chat/s4u_chat.py index 634d61355..149ecd9ea 100644 --- a/src/mais4u/mais4u_chat/s4u_chat.py +++ b/src/mais4u/mais4u_chat/s4u_chat.py @@ -155,13 +155,18 @@ class S4UChat: self._vip_queue = asyncio.PriorityQueue() self._normal_queue = asyncio.PriorityQueue() + # 优先级管理配置 + self.normal_queue_max_size = 20 # 普通队列最大容量,可以后续移到配置文件 + self.interest_dict = {} # 用户兴趣分字典,可以后续移到配置文件. e.g. {"user_id": 5.0} + self.at_bot_priority_bonus = 100.0 # @机器人时的额外优先分 + self._entry_counter = 0 # 保证FIFO的全局计数器 self._new_message_event = asyncio.Event() # 用于唤醒处理器 self._processing_task = asyncio.create_task(self._message_processor()) self._current_generation_task: Optional[asyncio.Task] = None - # 当前消息的元数据:(队列类型, 优先级, 计数器, 消息对象) - self._current_message_being_replied: Optional[Tuple[str, int, int, MessageRecv]] = None + # 当前消息的元数据:(队列类型, 优先级分数, 计数器, 消息对象) + self._current_message_being_replied: Optional[Tuple[str, float, int, MessageRecv]] = None self._is_replying = False self.gpt = S4UStreamGenerator() @@ -174,23 +179,35 @@ class S4UChat: vip_user_ids = [""] return message.message_info.user_info.user_id in vip_user_ids - def _get_message_priority(self, message: MessageRecv) -> int: - """为消息分配优先级。数字越小,优先级越高。""" + def _get_interest_score(self, user_id: str) -> float: + """获取用户的兴趣分,默认为1.0""" + return self.interest_dict.get(user_id, 1.0) + + def _calculate_base_priority_score(self, message: MessageRecv) -> float: + """ + 为消息计算基础优先级分数。分数越高,优先级越高。 + """ + score = 0.0 + # 如果消息 @ 了机器人,则增加一个很大的分数 if f"@{global_config.bot.nickname}" in message.processed_plain_text or any( f"@{alias}" in message.processed_plain_text for alias in global_config.bot.alias_names ): - return 0 - return 1 + score += self.at_bot_priority_bonus + + # 加上用户的固有兴趣分 + score += self._get_interest_score(message.message_info.user_info.user_id) + return score async def add_message(self, message: MessageRecv) -> None: """根据VIP状态和中断逻辑将消息放入相应队列。""" is_vip = self._is_vip(message) - new_priority = self._get_message_priority(message) + # 优先级分数越高,优先级越高。 + new_priority_score = self._calculate_base_priority_score(message) should_interrupt = False if self._current_generation_task and not self._current_generation_task.done(): if self._current_message_being_replied: - current_queue, current_priority, _, current_msg = self._current_message_being_replied + current_queue, current_priority_score, _, current_msg = self._current_message_being_replied # 规则:VIP从不被打断 if current_queue == "vip": @@ -207,11 +224,11 @@ class S4UChat: new_sender_id = message.message_info.user_info.user_id current_sender_id = current_msg.message_info.user_info.user_id # 新消息优先级更高 - if new_priority < current_priority: + if new_priority_score > current_priority_score: should_interrupt = True logger.info(f"[{self.stream_name}] New normal message has higher priority, interrupting.") - # 同用户,同级或更高级 - elif new_sender_id == current_sender_id and new_priority <= current_priority: + # 同用户,新消息的优先级不能更低 + elif new_sender_id == current_sender_id and new_priority_score >= current_priority_score: should_interrupt = True logger.info(f"[{self.stream_name}] Same user sent new message, interrupting.") @@ -220,12 +237,21 @@ class S4UChat: logger.warning(f"[{self.stream_name}] Interrupting reply. Already generated: '{self.gpt.partial_response}'") self._current_generation_task.cancel() - # 将消息放入对应的队列 - item = (new_priority, self._entry_counter, time.time(), message) + # asyncio.PriorityQueue 是最小堆,所以我们存入分数的相反数 + # 这样,原始分数越高的消息,在队列中的优先级数字越小,越靠前 + item = (-new_priority_score, self._entry_counter, time.time(), message) + if is_vip: await self._vip_queue.put(item) logger.info(f"[{self.stream_name}] VIP message added to queue.") else: + # 应用普通队列的最大容量限制 + if self._normal_queue.qsize() >= self.normal_queue_max_size: + # 队列已满,简单忽略新消息 + # 更复杂的逻辑(如替换掉队列中优先级最低的)对于 asyncio.PriorityQueue 来说实现复杂 + logger.debug(f"[{self.stream_name}] Normal queue is full, ignoring new message from {message.message_info.user_info.user_id}") + return + await self._normal_queue.put(item) self._entry_counter += 1 @@ -241,11 +267,13 @@ class S4UChat: # 优先处理VIP队列 if not self._vip_queue.empty(): - priority, entry_count, _, message = self._vip_queue.get_nowait() + neg_priority, entry_count, _, message = self._vip_queue.get_nowait() + priority = -neg_priority queue_name = "vip" # 其次处理普通队列 elif not self._normal_queue.empty(): - priority, entry_count, timestamp, message = self._normal_queue.get_nowait() + neg_priority, entry_count, timestamp, message = self._normal_queue.get_nowait() + priority = -neg_priority # 检查普通消息是否超时 if time.time() - timestamp > self._MESSAGE_TIMEOUT_SECONDS: logger.info(f"[{self.stream_name}] Discarding stale normal message: {message.processed_plain_text[:20]}...") diff --git a/src/mais4u/mais4u_chat/s4u_prompt.py b/src/mais4u/mais4u_chat/s4u_prompt.py index d2203bafe..c2aa4e654 100644 --- a/src/mais4u/mais4u_chat/s4u_prompt.py +++ b/src/mais4u/mais4u_chat/s4u_prompt.py @@ -1,6 +1,5 @@ from src.config.config import global_config from src.common.logger import get_logger -from src.individuality.individuality import get_individuality from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat import time diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 2bd9e24fe..365d6db4f 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "3.0.0" +version = "3.1.0" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请在修改后将version的值进行变更 @@ -44,6 +44,7 @@ compress_indentity = true # 是否压缩身份,压缩后会精简身份信息 [expression] # 表达方式 +enable_expression = true # 是否启用表达方式 expression_style = "描述麦麦说话的表达风格,表达习惯,例如:(请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景。)" enable_expression_learning = false # 是否启用表达学习,麦麦会学习不同群里人类说话风格(群之间不互通) learning_interval = 600 # 学习间隔 单位秒