From b5fd959fe136570e0c9fb69971d6e67dae2f6055 Mon Sep 17 00:00:00 2001 From: UnCLASPrommer Date: Tue, 15 Jul 2025 16:50:29 +0800 Subject: [PATCH] ruff, typing, api, bug fix --- changes.md | 1 + src/chat/emoji_system/emoji_manager.py | 2 +- src/chat/focus_chat/heartFC_chat.py | 2 + .../message_receive/uni_message_sender.py | 4 +- src/chat/planner_actions/planner.py | 4 +- src/chat/replyer/default_generator.py | 32 ++-- src/config/official_configs.py | 6 +- src/individuality/individuality.py | 17 +- src/individuality/personality.py | 10 +- src/mais4u/mais4u_chat/context_web_manager.py | 169 +++++++++--------- src/mais4u/mais4u_chat/loading.py | 5 +- src/mais4u/mais4u_chat/s4u_chat.py | 49 +++-- src/mais4u/mais4u_chat/s4u_mood_manager.py | 46 ++--- src/mais4u/mais4u_chat/s4u_msg_processor.py | 16 +- .../mais4u_chat/s4u_stream_generator.py | 1 - .../mais4u_chat/s4u_watching_manager.py | 63 +++---- src/mood/mood_manager.py | 17 +- src/plugin_system/apis/chat_api.py | 48 ++++- src/plugin_system/apis/emoji_api.py | 43 +++-- src/plugin_system/apis/generator_api.py | 10 +- src/plugin_system/apis/llm_api.py | 16 +- src/plugin_system/apis/send_api.py | 10 +- src/plugins/built_in/core_actions/plugin.py | 2 +- 23 files changed, 335 insertions(+), 238 deletions(-) diff --git a/changes.md b/changes.md index 7ec499b43..4986e4d60 100644 --- a/changes.md +++ b/changes.md @@ -20,3 +20,4 @@ - `chat_api.py`中获取流的参数中可以使用一个特殊的枚举类型来获得所有平台的 ChatStream 了。 - `config_api.py`中的`get_global_config`和`get_plugin_config`方法现在支持嵌套访问的配置键名。 - `database_api.py`中的`db_query`方法调整了参数顺序以增强参数限制的同时,保证了typing正确;`db_get`方法增加了`single_result`参数,与`db_query`保持一致。 +4. 现在增加了参数类型检查,完善了对应注释 \ No newline at end of file diff --git a/src/chat/emoji_system/emoji_manager.py b/src/chat/emoji_system/emoji_manager.py index 578ff0172..dd9f12c0d 100644 --- a/src/chat/emoji_system/emoji_manager.py +++ b/src/chat/emoji_system/emoji_manager.py @@ -47,7 +47,7 @@ class MaiEmoji: self.embedding = [] self.hash = "" # 初始为空,在创建实例时会计算 self.description = "" - self.emotion = [] + self.emotion: List[str] = [] self.usage_count = 0 self.last_used_time = time.time() self.register_time = time.time() diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 0978b4b59..dd71da7b1 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -243,6 +243,8 @@ class HeartFChatting: loop_start_time = time.time() await self.relationship_builder.build_relation() + available_actions = {} + # 第一步:动作修改 with Timer("动作修改", cycle_timers): try: diff --git a/src/chat/message_receive/uni_message_sender.py b/src/chat/message_receive/uni_message_sender.py index 067ae19a2..a881549f5 100644 --- a/src/chat/message_receive/uni_message_sender.py +++ b/src/chat/message_receive/uni_message_sender.py @@ -38,7 +38,9 @@ class HeartFCSender: def __init__(self): self.storage = MessageStorage() - async def send_message(self, message: MessageSending, typing=False, set_reply=False, storage_message=True, show_log=True): + async def send_message( + self, message: MessageSending, typing=False, set_reply=False, storage_message=True, show_log=True + ): """ 处理、发送并存储一条消息。 diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index cbd4c23ef..23f1d6948 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -79,7 +79,9 @@ class ActionPlanner: self.last_obs_time_mark = 0.0 - async def plan(self, mode: ChatMode = ChatMode.FOCUS) -> Dict[str, Dict[str, Any] | str]: # sourcery skip: dict-comprehension + async def plan( + self, mode: ChatMode = ChatMode.FOCUS + ) -> Dict[str, Dict[str, Any] | str]: # sourcery skip: dict-comprehension """ 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 """ diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 0b6e23aca..de07d5136 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -480,18 +480,18 @@ class DefaultReplyer: def build_s4u_chat_history_prompts(self, message_list_before_now: list, target_user_id: str) -> tuple[str, str]: """ 构建 s4u 风格的分离对话 prompt - + Args: message_list_before_now: 历史消息列表 target_user_id: 目标用户ID(当前对话对象) - + Returns: tuple: (核心对话prompt, 背景对话prompt) """ core_dialogue_list = [] background_dialogue_list = [] bot_id = str(global_config.bot.qq_account) - + # 过滤消息:分离bot和目标用户的对话 vs 其他用户的对话 for msg_dict in message_list_before_now: try: @@ -504,11 +504,11 @@ class DefaultReplyer: background_dialogue_list.append(msg_dict) except Exception as e: logger.error(f"无法处理历史消息记录: {msg_dict}, 错误: {e}") - + # 构建背景对话 prompt background_dialogue_prompt = "" if background_dialogue_list: - latest_25_msgs = background_dialogue_list[-int(global_config.chat.max_context_size*0.6):] + latest_25_msgs = background_dialogue_list[-int(global_config.chat.max_context_size * 0.6) :] background_dialogue_prompt_str = build_readable_messages( latest_25_msgs, replace_bot_name=True, @@ -517,12 +517,12 @@ class DefaultReplyer: show_pic=False, ) background_dialogue_prompt = f"这是其他用户的发言:\n{background_dialogue_prompt_str}" - + # 构建核心对话 prompt core_dialogue_prompt = "" if core_dialogue_list: - core_dialogue_list = core_dialogue_list[-int(global_config.chat.max_context_size*2):] # 限制消息数量 - + core_dialogue_list = core_dialogue_list[-int(global_config.chat.max_context_size * 2) :] # 限制消息数量 + core_dialogue_prompt_str = build_readable_messages( core_dialogue_list, replace_bot_name=True, @@ -533,7 +533,7 @@ class DefaultReplyer: show_actions=True, ) core_dialogue_prompt = core_dialogue_prompt_str - + return core_dialogue_prompt, background_dialogue_prompt async def build_prompt_reply_context( @@ -579,14 +579,13 @@ class DefaultReplyer: action_description = action_info.description action_descriptions += f"- {action_name}: {action_description}\n" action_descriptions += "\n" - + message_list_before_now_long = get_raw_msg_before_timestamp_with_chat( chat_id=chat_id, timestamp=time.time(), limit=global_config.chat.max_context_size * 2, ) - - + message_list_before_now = get_raw_msg_before_timestamp_with_chat( chat_id=chat_id, timestamp=time.time(), @@ -713,8 +712,6 @@ class DefaultReplyer: # 根据sender通过person_info_manager反向查找person_id,再获取user_id person_id = person_info_manager.get_person_id_by_person_name(sender) - - # 根据配置选择使用哪种 prompt 构建模式 if global_config.chat.use_s4u_prompt_mode and person_id: # 使用 s4u 对话构建模式:分离当前对话对象和其他对话 @@ -725,16 +722,15 @@ class DefaultReplyer: except Exception as e: logger.warning(f"无法从person_id {person_id} 获取user_id: {e}") target_user_id = "" - - + # 构建分离的对话 prompt core_dialogue_prompt, background_dialogue_prompt = self.build_s4u_chat_history_prompts( message_list_before_now_long, target_user_id ) - + # 使用 s4u 风格的模板 template_name = "s4u_style_prompt" - + return await global_prompt_manager.format_prompt( template_name, expression_habits_block=expression_habits_block, diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 4433bae44..bb40687b6 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -37,7 +37,7 @@ class PersonalityConfig(ConfigBase): personality_side: str """人格侧写""" - + identity: str = "" """身份特征""" @@ -106,7 +106,6 @@ class ChatConfig(ConfigBase): focus_value: float = 1.0 """麦麦的专注思考能力,越低越容易专注,消耗token也越多""" - def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float: """ 根据当前时间和聊天流获取对应的 talk_frequency @@ -246,6 +245,7 @@ class ChatConfig(ConfigBase): except (ValueError, IndexError): return None + @dataclass class MessageReceiveConfig(ConfigBase): """消息接收配置类""" @@ -274,8 +274,6 @@ class NormalChatConfig(ConfigBase): """@bot 必然回复""" - - @dataclass class ExpressionConfig(ConfigBase): """表达配置类""" diff --git a/src/individuality/individuality.py b/src/individuality/individuality.py index 878e00455..9e769a3ff 100644 --- a/src/individuality/individuality.py +++ b/src/individuality/individuality.py @@ -63,11 +63,11 @@ class Individuality: personality_side: 人格侧面描述 identity: 身份细节描述 """ - bot_nickname=global_config.bot.nickname - personality_core=global_config.personality.personality_core - personality_side=global_config.personality.personality_side - identity=global_config.personality.identity - + bot_nickname = global_config.bot.nickname + personality_core = global_config.personality.personality_core + personality_side = global_config.personality.personality_side + identity = global_config.personality.identity + logger.info("正在初始化个体特征") person_info_manager = get_person_info_manager() self.bot_person_id = person_info_manager.get_person_id("system", "bot_id") @@ -168,11 +168,10 @@ class Individuality: else: logger.error("人设构建失败") - async def get_personality_block(self) -> str: person_info_manager = get_person_info_manager() bot_person_id = person_info_manager.get_person_id("system", "bot_id") - + bot_name = global_config.bot.nickname if global_config.bot.alias_names: bot_nickname = f",也有人叫你{','.join(global_config.bot.alias_names)}" @@ -197,9 +196,8 @@ class Individuality: identity = short_impression[1] prompt_personality = f"{personality},{identity}" identity_block = f"你的名字是{bot_name}{bot_nickname},你{prompt_personality}:" - - return identity_block + return identity_block def _get_config_hash( self, bot_nickname: str, personality_core: str, personality_side: str, identity: list @@ -295,7 +293,6 @@ class Individuality: except IOError as e: logger.error(f"保存meta_info文件失败: {e}") - async def _create_personality(self, personality_core: str, personality_side: str) -> str: # sourcery skip: merge-list-append, move-assign """使用LLM创建压缩版本的impression diff --git a/src/individuality/personality.py b/src/individuality/personality.py index 5d666101e..5e9988f66 100644 --- a/src/individuality/personality.py +++ b/src/individuality/personality.py @@ -42,7 +42,15 @@ class Personality: return cls._instance @classmethod - def initialize(cls, bot_nickname: str, personality_core: str, personality_side: str, identity: List[str] = None, compress_personality: bool = True, compress_identity: bool = True) -> "Personality": + def initialize( + cls, + bot_nickname: str, + personality_core: str, + personality_side: str, + identity: List[str] = None, + compress_personality: bool = True, + compress_identity: bool = True, + ) -> "Personality": """初始化人格特质 Args: diff --git a/src/mais4u/mais4u_chat/context_web_manager.py b/src/mais4u/mais4u_chat/context_web_manager.py index 0c3ac4f6a..adf6414cd 100644 --- a/src/mais4u/mais4u_chat/context_web_manager.py +++ b/src/mais4u/mais4u_chat/context_web_manager.py @@ -16,27 +16,27 @@ logger = get_logger("context_web") class ContextMessage: """上下文消息类""" - + def __init__(self, message: MessageRecv): self.user_name = message.message_info.user_info.user_nickname self.user_id = message.message_info.user_info.user_id self.content = message.processed_plain_text self.timestamp = datetime.now() self.group_name = message.message_info.group_info.group_name if message.message_info.group_info else "私聊" - + def to_dict(self): return { "user_name": self.user_name, "user_id": self.user_id, "content": self.content, "timestamp": self.timestamp.strftime("%m-%d %H:%M:%S"), - "group_name": self.group_name + "group_name": self.group_name, } class ContextWebManager: """上下文网页管理器""" - + def __init__(self, max_messages: int = 10, port: int = 8765): self.max_messages = max_messages self.port = port @@ -46,53 +46,53 @@ class ContextWebManager: self.runner = None self.site = None self._server_starting = False # 添加启动标志防止并发 - + async def start_server(self): """启动web服务器""" if self.site is not None: logger.debug("Web服务器已经启动,跳过重复启动") return - + if self._server_starting: logger.debug("Web服务器正在启动中,等待启动完成...") # 等待启动完成 while self._server_starting and self.site is None: await asyncio.sleep(0.1) return - + self._server_starting = True - + try: self.app = web.Application() - + # 设置CORS - cors = aiohttp_cors.setup(self.app, defaults={ - "*": aiohttp_cors.ResourceOptions( - allow_credentials=True, - expose_headers="*", - allow_headers="*", - allow_methods="*" - ) - }) - + cors = aiohttp_cors.setup( + self.app, + defaults={ + "*": aiohttp_cors.ResourceOptions( + allow_credentials=True, expose_headers="*", allow_headers="*", allow_methods="*" + ) + }, + ) + # 添加路由 - self.app.router.add_get('/', self.index_handler) - self.app.router.add_get('/ws', self.websocket_handler) - self.app.router.add_get('/api/contexts', self.get_contexts_handler) - self.app.router.add_get('/debug', self.debug_handler) - + self.app.router.add_get("/", self.index_handler) + self.app.router.add_get("/ws", self.websocket_handler) + self.app.router.add_get("/api/contexts", self.get_contexts_handler) + self.app.router.add_get("/debug", self.debug_handler) + # 为所有路由添加CORS for route in list(self.app.router.routes()): cors.add(route) - + self.runner = web.AppRunner(self.app) await self.runner.setup() - - self.site = web.TCPSite(self.runner, 'localhost', self.port) + + self.site = web.TCPSite(self.runner, "localhost", self.port) await self.site.start() - + logger.info(f"🌐 上下文网页服务器启动成功在 http://localhost:{self.port}") - + except Exception as e: logger.error(f"❌ 启动Web服务器失败: {e}") # 清理部分启动的资源 @@ -104,7 +104,7 @@ class ContextWebManager: raise finally: self._server_starting = False - + async def stop_server(self): """停止web服务器""" if self.site: @@ -115,10 +115,11 @@ class ContextWebManager: self.runner = None self.site = None self._server_starting = False - + async def index_handler(self, request): """主页处理器""" - html_content = ''' + html_content = ( + """ @@ -231,7 +232,9 @@ class ContextWebManager: function connectWebSocket() { console.log('正在连接WebSocket...'); - ws = new WebSocket('ws://localhost:''' + str(self.port) + '''/ws'); + ws = new WebSocket('ws://localhost:""" + + str(self.port) + + """/ws'); ws.onopen = function() { console.log('WebSocket连接已建立'); @@ -402,47 +405,48 @@ class ContextWebManager: - ''' - return web.Response(text=html_content, content_type='text/html') - + """ + ) + return web.Response(text=html_content, content_type="text/html") + async def websocket_handler(self, request): """WebSocket处理器""" ws = web.WebSocketResponse() await ws.prepare(request) - + self.websockets.append(ws) logger.debug(f"WebSocket连接建立,当前连接数: {len(self.websockets)}") - + # 发送初始数据 await self.send_contexts_to_websocket(ws) - + async for msg in ws: if msg.type == WSMsgType.ERROR: - logger.error(f'WebSocket错误: {ws.exception()}') + logger.error(f"WebSocket错误: {ws.exception()}") break - + # 清理断开的连接 if ws in self.websockets: self.websockets.remove(ws) logger.debug(f"WebSocket连接断开,当前连接数: {len(self.websockets)}") - + return ws - + async def get_contexts_handler(self, request): """获取上下文API""" all_context_msgs = [] for chat_id, contexts in self.contexts.items(): all_context_msgs.extend(list(contexts)) - + # 按时间排序,最新的在最后 all_context_msgs.sort(key=lambda x: x.timestamp) - + # 转换为字典格式 - contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages:]] - + contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages :]] + logger.debug(f"返回上下文数据,共 {len(contexts_data)} 条消息") return web.json_response({"contexts": contexts_data}) - + async def debug_handler(self, request): """调试信息处理器""" debug_info = { @@ -451,7 +455,7 @@ class ContextWebManager: "total_chats": len(self.contexts), "total_messages": sum(len(contexts) for contexts in self.contexts.values()), } - + # 构建聊天详情HTML chats_html = "" for chat_id, contexts in self.contexts.items(): @@ -460,15 +464,15 @@ class ContextWebManager: timestamp = msg.timestamp.strftime("%H:%M:%S") content = msg.content[:50] + "..." if len(msg.content) > 50 else msg.content messages_html += f'
[{timestamp}] {msg.user_name}: {content}
' - - chats_html += f''' + + chats_html += f"""

聊天 {chat_id} ({len(contexts)} 条消息)

{messages_html}
- ''' - - html_content = f''' + """ + + html_content = f""" @@ -510,74 +514,78 @@ class ContextWebManager: - ''' - - return web.Response(text=html_content, content_type='text/html') - + """ + + return web.Response(text=html_content, content_type="text/html") + async def add_message(self, chat_id: str, message: MessageRecv): """添加新消息到上下文""" if chat_id not in self.contexts: self.contexts[chat_id] = deque(maxlen=self.max_messages) logger.debug(f"为聊天 {chat_id} 创建新的上下文队列") - + context_msg = ContextMessage(message) self.contexts[chat_id].append(context_msg) - + # 统计当前总消息数 total_messages = sum(len(contexts) for contexts in self.contexts.values()) - - logger.info(f"✅ 添加消息到上下文 [总数: {total_messages}]: [{context_msg.group_name}] {context_msg.user_name}: {context_msg.content}") - + + logger.info( + f"✅ 添加消息到上下文 [总数: {total_messages}]: [{context_msg.group_name}] {context_msg.user_name}: {context_msg.content}" + ) + # 调试:打印当前所有消息 logger.info(f"📝 当前上下文中的所有消息:") for cid, contexts in self.contexts.items(): logger.info(f" 聊天 {cid}: {len(contexts)} 条消息") for i, msg in enumerate(contexts): - logger.info(f" {i+1}. [{msg.timestamp.strftime('%H:%M:%S')}] {msg.user_name}: {msg.content[:30]}...") - + logger.info( + f" {i + 1}. [{msg.timestamp.strftime('%H:%M:%S')}] {msg.user_name}: {msg.content[:30]}..." + ) + # 广播更新给所有WebSocket连接 await self.broadcast_contexts() - + async def send_contexts_to_websocket(self, ws: web.WebSocketResponse): """向单个WebSocket发送上下文数据""" all_context_msgs = [] for chat_id, contexts in self.contexts.items(): all_context_msgs.extend(list(contexts)) - + # 按时间排序,最新的在最后 all_context_msgs.sort(key=lambda x: x.timestamp) - + # 转换为字典格式 - contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages:]] - + contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages :]] + data = {"contexts": contexts_data} await ws.send_str(json.dumps(data, ensure_ascii=False)) - + async def broadcast_contexts(self): """向所有WebSocket连接广播上下文更新""" if not self.websockets: logger.debug("没有WebSocket连接,跳过广播") return - + all_context_msgs = [] for chat_id, contexts in self.contexts.items(): all_context_msgs.extend(list(contexts)) - + # 按时间排序,最新的在最后 all_context_msgs.sort(key=lambda x: x.timestamp) - + # 转换为字典格式 - contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages:]] - + contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages :]] + data = {"contexts": contexts_data} message = json.dumps(data, ensure_ascii=False) - + logger.info(f"广播 {len(contexts_data)} 条消息到 {len(self.websockets)} 个WebSocket连接") - + # 创建WebSocket列表的副本,避免在遍历时修改 websockets_copy = self.websockets.copy() removed_count = 0 - + for ws in websockets_copy: if ws.closed: if ws in self.websockets: @@ -592,7 +600,7 @@ class ContextWebManager: if ws in self.websockets: self.websockets.remove(ws) removed_count += 1 - + if removed_count > 0: logger.debug(f"清理了 {removed_count} 个断开的WebSocket连接") @@ -613,5 +621,4 @@ async def init_context_web_manager(): """初始化上下文网页管理器""" manager = get_context_web_manager() await manager.start_server() - return manager - + return manager diff --git a/src/mais4u/mais4u_chat/loading.py b/src/mais4u/mais4u_chat/loading.py index 50b3e43c9..ec6ae5d9c 100644 --- a/src/mais4u/mais4u_chat/loading.py +++ b/src/mais4u/mais4u_chat/loading.py @@ -11,6 +11,7 @@ from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.manager.async_task_manager import AsyncTask, async_task_manager from src.plugin_system.apis import send_api + async def send_loading(chat_id: str, content: str): await send_api.custom_to_stream( message_type="loading", @@ -19,7 +20,8 @@ async def send_loading(chat_id: str, content: str): storage_message=False, show_log=True, ) - + + async def send_unloading(chat_id: str): await send_api.custom_to_stream( message_type="loading", @@ -28,4 +30,3 @@ async def send_unloading(chat_id: str): storage_message=False, show_log=True, ) - \ No newline at end of file diff --git a/src/mais4u/mais4u_chat/s4u_chat.py b/src/mais4u/mais4u_chat/s4u_chat.py index 641da89b0..344c85eb5 100644 --- a/src/mais4u/mais4u_chat/s4u_chat.py +++ b/src/mais4u/mais4u_chat/s4u_chat.py @@ -29,7 +29,6 @@ class MessageSenderContainer: self._task: Optional[asyncio.Task] = None self._paused_event = asyncio.Event() self._paused_event.set() # 默认设置为非暂停状态 - async def add_message(self, chunk: str): """向队列中添加一个消息块。""" @@ -201,10 +200,10 @@ class S4UChat: 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 + # f"@{alias}" in message.processed_plain_text for alias in global_config.bot.alias_names # ): - # score += self.at_bot_priority_bonus - + # score += self.at_bot_priority_bonus + # 加上消息自带的优先级 score += priority_info.get("message_priority", 0.0) @@ -214,9 +213,9 @@ class S4UChat: async def add_message(self, message: MessageRecv) -> None: """根据VIP状态和中断逻辑将消息放入相应队列。""" - + await self.relationship_builder.build_relation() - + priority_info = self._get_priority_info(message) is_vip = self._is_vip(priority_info) new_priority_score = self._calculate_base_priority_score(message, priority_info) @@ -273,36 +272,38 @@ class S4UChat: """清理普通队列中不在最近N条消息范围内的消息""" if self._normal_queue.empty(): return - + # 计算阈值:保留最近 recent_message_keep_count 条消息 cutoff_counter = max(0, self._entry_counter - self.recent_message_keep_count) - + # 临时存储需要保留的消息 temp_messages = [] removed_count = 0 - + # 取出所有普通队列中的消息 while not self._normal_queue.empty(): try: item = self._normal_queue.get_nowait() neg_priority, entry_count, timestamp, message = item - + # 如果消息在最近N条消息范围内,保留它 if entry_count >= cutoff_counter: temp_messages.append(item) else: removed_count += 1 self._normal_queue.task_done() # 标记被移除的任务为完成 - + except asyncio.QueueEmpty: break - + # 将保留的消息重新放入队列 for item in temp_messages: self._normal_queue.put_nowait(item) - + if removed_count > 0: - logger.info(f"[{self.stream_name}] Cleaned up {removed_count} old normal messages outside recent {self.recent_message_keep_count} range.") + logger.info( + f"[{self.stream_name}] Cleaned up {removed_count} old normal messages outside recent {self.recent_message_keep_count} range." + ) async def _message_processor(self): """调度器:优先处理VIP队列,然后处理普通队列。""" @@ -311,7 +312,7 @@ class S4UChat: # 等待有新消息的信号,避免空转 await self._new_message_event.wait() self._new_message_event.clear() - + # 清理普通队列中的过旧消息 self._cleanup_old_normal_messages() @@ -372,16 +373,16 @@ class S4UChat: async def _generate_and_send(self, message: MessageRecv): """为单个消息生成文本回复。整个过程可以被中断。""" self._is_replying = True - + await send_loading(self.stream_id, "......") - + # 视线管理:开始生成回复时切换视线状态 chat_watching = watching_manager.get_watching_by_chat_id(self.stream_id) await chat_watching.on_reply_start() - + # 回复生成实时展示:开始生成 user_name = message.message_info.user_info.user_nickname - + sender_container = MessageSenderContainer(self.chat_stream, message) sender_container.start() @@ -395,13 +396,11 @@ class S4UChat: # a. 发送文本块 await sender_container.add_message(chunk) - # 等待所有文本消息发送完成 await sender_container.close() await sender_container.join() - - + logger.info(f"[{self.stream_name}] 所有文本块处理完毕。") except asyncio.CancelledError: @@ -412,13 +411,13 @@ class S4UChat: # 回复生成实时展示:清空内容(出错时) finally: self._is_replying = False - + await send_unloading(self.stream_id) - + # 视线管理:回复结束时切换视线状态 chat_watching = watching_manager.get_watching_by_chat_id(self.stream_id) await chat_watching.on_reply_finished() - + # 确保发送器被妥善关闭(即使已关闭,再次调用也是安全的) sender_container.resume() if not sender_container._task.done(): diff --git a/src/mais4u/mais4u_chat/s4u_mood_manager.py b/src/mais4u/mais4u_chat/s4u_mood_manager.py index a394e9429..0c982eb63 100644 --- a/src/mais4u/mais4u_chat/s4u_mood_manager.py +++ b/src/mais4u/mais4u_chat/s4u_mood_manager.py @@ -125,7 +125,7 @@ class ChatMood: ) self.last_change_time = 0 - + # 发送初始情绪状态到ws端 asyncio.create_task(self.send_emotion_update(self.mood_values)) @@ -231,10 +231,10 @@ class ChatMood: if numerical_mood_response: _old_mood_values = self.mood_values.copy() self.mood_values = numerical_mood_response - + # 发送情绪更新到ws端 await self.send_emotion_update(self.mood_values) - + logger.info(f"[{self.chat_id}] 情绪变化: {_old_mood_values} -> {self.mood_values}") self.last_change_time = message_time @@ -308,10 +308,10 @@ class ChatMood: if numerical_mood_response: _old_mood_values = self.mood_values.copy() self.mood_values = numerical_mood_response - + # 发送情绪更新到ws端 await self.send_emotion_update(self.mood_values) - + logger.info(f"[{self.chat_id}] 情绪回归: {_old_mood_values} -> {self.mood_values}") self.regression_count += 1 @@ -322,9 +322,9 @@ class ChatMood: "joy": mood_values.get("joy", 5), "anger": mood_values.get("anger", 1), "sorrow": mood_values.get("sorrow", 1), - "fear": mood_values.get("fear", 1) + "fear": mood_values.get("fear", 1), } - + await send_api.custom_to_stream( message_type="emotion", content=emotion_data, @@ -332,7 +332,7 @@ class ChatMood: storage_message=False, show_log=True, ) - + logger.info(f"[{self.chat_id}] 发送情绪更新: {emotion_data}") @@ -345,27 +345,27 @@ class MoodRegressionTask(AsyncTask): async def run(self): self.run_count += 1 logger.info(f"[回归任务] 第{self.run_count}次检查,当前管理{len(self.mood_manager.mood_list)}个聊天的情绪状态") - + now = time.time() regression_executed = 0 - + for mood in self.mood_manager.mood_list: chat_info = f"chat {mood.chat_id}" - + if mood.last_change_time == 0: logger.debug(f"[回归任务] {chat_info} 尚未有情绪变化,跳过回归") continue time_since_last_change = now - mood.last_change_time - + # 检查是否有极端情绪需要快速回归 high_emotions = {k: v for k, v in mood.mood_values.items() if v >= 8} has_extreme_emotion = len(high_emotions) > 0 - + # 回归条件:1. 正常时间间隔(120s) 或 2. 有极端情绪且距上次变化>=30s should_regress = False regress_reason = "" - + if time_since_last_change > 120: should_regress = True regress_reason = f"常规回归(距上次变化{int(time_since_last_change)}秒)" @@ -373,24 +373,28 @@ class MoodRegressionTask(AsyncTask): should_regress = True high_emotion_str = ", ".join([f"{k}={v}" for k, v in high_emotions.items()]) regress_reason = f"极端情绪快速回归({high_emotion_str}, 距上次变化{int(time_since_last_change)}秒)" - + if should_regress: if mood.regression_count >= 3: logger.debug(f"[回归任务] {chat_info} 已达到最大回归次数(3次),停止回归") continue - logger.info(f"[回归任务] {chat_info} 开始情绪回归 ({regress_reason},第{mood.regression_count + 1}次回归)") + logger.info( + f"[回归任务] {chat_info} 开始情绪回归 ({regress_reason},第{mood.regression_count + 1}次回归)" + ) await mood.regress_mood() regression_executed += 1 else: if has_extreme_emotion: remaining_time = 5 - time_since_last_change high_emotion_str = ", ".join([f"{k}={v}" for k, v in high_emotions.items()]) - logger.debug(f"[回归任务] {chat_info} 存在极端情绪({high_emotion_str}),距离快速回归还需等待{int(remaining_time)}秒") + logger.debug( + f"[回归任务] {chat_info} 存在极端情绪({high_emotion_str}),距离快速回归还需等待{int(remaining_time)}秒" + ) else: remaining_time = 120 - time_since_last_change logger.debug(f"[回归任务] {chat_info} 距离回归还需等待{int(remaining_time)}秒") - + if regression_executed > 0: logger.info(f"[回归任务] 本次执行了{regression_executed}个聊天的情绪回归") else: @@ -409,11 +413,11 @@ class MoodManager: return logger.info("启动情绪管理任务...") - + # 启动情绪回归任务 regression_task = MoodRegressionTask(self) await async_task_manager.add_task(regression_task) - + self.task_started = True logger.info("情绪管理任务已启动(情绪回归)") @@ -435,7 +439,7 @@ class MoodManager: # 发送重置后的情绪状态到ws端 asyncio.create_task(mood.send_emotion_update(mood.mood_values)) return - + # 如果没有找到现有的mood,创建新的 new_mood = ChatMood(chat_id) self.mood_list.append(new_mood) diff --git a/src/mais4u/mais4u_chat/s4u_msg_processor.py b/src/mais4u/mais4u_chat/s4u_msg_processor.py index 86ea90275..68194c2bf 100644 --- a/src/mais4u/mais4u_chat/s4u_msg_processor.py +++ b/src/mais4u/mais4u_chat/s4u_msg_processor.py @@ -103,7 +103,7 @@ class S4UMessageProcessor: await s4u_chat.add_message(message) interested_rate, _ = await _calculate_interest(message) - + await mood_manager.start() chat_mood = mood_manager.get_mood_by_chat_id(chat.stream_id) @@ -111,7 +111,7 @@ class S4UMessageProcessor: chat_action = action_manager.get_action_state_by_chat_id(chat.stream_id) asyncio.create_task(chat_action.update_action_by_message(message)) # asyncio.create_task(chat_action.update_facial_expression_by_message(message, interested_rate)) - + # 视线管理:收到消息时切换视线状态 chat_watching = watching_manager.get_watching_by_chat_id(chat.stream_id) asyncio.create_task(chat_watching.on_message_received()) @@ -124,25 +124,25 @@ class S4UMessageProcessor: async def _handle_context_web_update(self, chat_id: str, message: MessageRecv): """处理上下文网页更新的独立task - + Args: chat_id: 聊天ID message: 消息对象 """ try: logger.debug(f"🔄 开始处理上下文网页更新: {message.message_info.user_info.user_nickname}") - + context_manager = get_context_web_manager() - + # 只在服务器未启动时启动(避免重复启动) if context_manager.site is None: logger.info("🚀 首次启动上下文网页服务器...") await context_manager.start_server() - + # 添加消息到上下文并更新网页 await context_manager.add_message(chat_id, message) - + logger.debug(f"✅ 上下文网页更新完成: {message.message_info.user_info.user_nickname}") - + except Exception as e: logger.error(f"❌ 处理上下文网页更新失败: {e}", exc_info=True) diff --git a/src/mais4u/mais4u_chat/s4u_stream_generator.py b/src/mais4u/mais4u_chat/s4u_stream_generator.py index 09d838bdd..7a2c78042 100644 --- a/src/mais4u/mais4u_chat/s4u_stream_generator.py +++ b/src/mais4u/mais4u_chat/s4u_stream_generator.py @@ -107,7 +107,6 @@ class S4UStreamGenerator: model_name: str, **kwargs, ) -> AsyncGenerator[str, None]: - buffer = "" delimiters = ",。!?,.!?\n\r" # For final trimming punctuation_buffer = "" diff --git a/src/mais4u/mais4u_chat/s4u_watching_manager.py b/src/mais4u/mais4u_chat/s4u_watching_manager.py index 897ef7f70..0ef684340 100644 --- a/src/mais4u/mais4u_chat/s4u_watching_manager.py +++ b/src/mais4u/mais4u_chat/s4u_watching_manager.py @@ -43,23 +43,24 @@ logger = get_logger("watching") class WatchingState(Enum): """视线状态枚举""" + WANDERING = "wandering" # 随意看 - DANMU = "danmu" # 看弹幕 - LENS = "lens" # 看镜头 + DANMU = "danmu" # 看弹幕 + LENS = "lens" # 看镜头 class ChatWatching: def __init__(self, chat_id: str): self.chat_id: str = chat_id self.current_state: WatchingState = WatchingState.LENS # 默认看镜头 - self.last_sent_state: Optional[WatchingState] = None # 上次发送的状态 - self.state_needs_update: bool = True # 是否需要更新状态 - + self.last_sent_state: Optional[WatchingState] = None # 上次发送的状态 + self.state_needs_update: bool = True # 是否需要更新状态 + # 状态切换相关 - self.is_replying: bool = False # 是否正在生成回复 - self.reply_finished_time: Optional[float] = None # 回复完成时间 - self.danmu_viewing_duration: float = 1.0 # 看弹幕持续时间(秒) - + self.is_replying: bool = False # 是否正在生成回复 + self.reply_finished_time: Optional[float] = None # 回复完成时间 + self.danmu_viewing_duration: float = 1.0 # 看弹幕持续时间(秒) + logger.info(f"[{self.chat_id}] 视线管理器初始化,默认状态: {self.current_state.value}") async def _change_state(self, new_state: WatchingState, reason: str = ""): @@ -69,7 +70,7 @@ class ChatWatching: self.current_state = new_state self.state_needs_update = True logger.info(f"[{self.chat_id}] 视线状态切换: {old_state.value} → {new_state.value} ({reason})") - + # 立即发送视线状态更新 await self._send_watching_update() else: @@ -86,7 +87,7 @@ class ChatWatching: """开始生成回复时调用""" self.is_replying = True self.reply_finished_time = None - + if look_at_lens: await self._change_state(WatchingState.LENS, "开始生成回复-看镜头") else: @@ -96,35 +97,29 @@ class ChatWatching: """生成回复完毕时调用""" self.is_replying = False self.reply_finished_time = time.time() - + # 先看弹幕1秒 await self._change_state(WatchingState.DANMU, "回复完毕-看弹幕") logger.info(f"[{self.chat_id}] 回复完毕,将看弹幕{self.danmu_viewing_duration}秒后转为看镜头") - + # 设置定时器,1秒后自动切换到看镜头 asyncio.create_task(self._auto_switch_to_lens()) async def _auto_switch_to_lens(self): """自动切换到看镜头(延迟执行)""" await asyncio.sleep(self.danmu_viewing_duration) - + # 检查是否仍需要切换(可能状态已经被其他事件改变) - if (self.reply_finished_time is not None and - self.current_state == WatchingState.DANMU and - not self.is_replying): - + if self.reply_finished_time is not None and self.current_state == WatchingState.DANMU and not self.is_replying: await self._change_state(WatchingState.LENS, "看弹幕时间结束") self.reply_finished_time = None # 重置完成时间 async def _send_watching_update(self): """立即发送视线状态更新""" await send_api.custom_to_stream( - message_type="watching", - content=self.current_state.value, - stream_id=self.chat_id, - storage_message=False + message_type="watching", content=self.current_state.value, stream_id=self.chat_id, storage_message=False ) - + logger.info(f"[{self.chat_id}] 发送视线状态更新: {self.current_state.value}") self.last_sent_state = self.current_state self.state_needs_update = False @@ -139,11 +134,10 @@ class ChatWatching: "current_state": self.current_state.value, "is_replying": self.is_replying, "reply_finished_time": self.reply_finished_time, - "state_needs_update": self.state_needs_update + "state_needs_update": self.state_needs_update, } - class WatchingManager: def __init__(self): self.watching_list: list[ChatWatching] = [] @@ -156,7 +150,7 @@ class WatchingManager: return logger.info("启动视线管理系统...") - + self.task_started = True logger.info("视线管理系统已启动(状态变化时立即发送)") @@ -169,10 +163,10 @@ class WatchingManager: new_watching = ChatWatching(chat_id) self.watching_list.append(new_watching) logger.info(f"为chat {chat_id}创建新的视线管理器") - + # 发送初始状态 asyncio.create_task(new_watching._send_watching_update()) - + return new_watching def reset_watching_by_chat_id(self, chat_id: str): @@ -185,27 +179,24 @@ class WatchingManager: watching.is_replying = False watching.reply_finished_time = None logger.info(f"[{chat_id}] 视线状态已重置为默认状态") - + # 发送重置后的状态 asyncio.create_task(watching._send_watching_update()) return - + # 如果没有找到现有的watching,创建新的 new_watching = ChatWatching(chat_id) self.watching_list.append(new_watching) logger.info(f"为chat {chat_id}创建并重置视线管理器") - + # 发送初始状态 asyncio.create_task(new_watching._send_watching_update()) def get_all_watching_info(self) -> dict: """获取所有聊天的视线状态信息(用于调试)""" - return { - watching.chat_id: watching.get_state_info() - for watching in self.watching_list - } + return {watching.chat_id: watching.get_state_info() for watching in self.watching_list} # 全局视线管理器实例 watching_manager = WatchingManager() -"""全局视线管理器""" \ No newline at end of file +"""全局视线管理器""" diff --git a/src/mood/mood_manager.py b/src/mood/mood_manager.py index acd22fd5b..b47785401 100644 --- a/src/mood/mood_manager.py +++ b/src/mood/mood_manager.py @@ -46,10 +46,10 @@ def init_prompt(): class ChatMood: def __init__(self, chat_id: str): self.chat_id: str = chat_id - + chat_manager = get_chat_manager() self.chat_stream = chat_manager.get_stream(self.chat_id) - + self.log_prefix = f"[{self.chat_stream.group_info.group_name if self.chat_stream.group_info else self.chat_stream.user_info.user_nickname}]" self.mood_state: str = "感觉很平静" @@ -92,7 +92,7 @@ class ChatMood: chat_id=self.chat_id, timestamp_start=self.last_change_time, timestamp_end=message_time, - limit=int(global_config.chat.max_context_size/3), + limit=int(global_config.chat.max_context_size / 3), limit_mode="last", ) chat_talking_prompt = build_readable_messages( @@ -121,14 +121,12 @@ class ChatMood: mood_state=self.mood_state, ) - - response, (reasoning_content, model_name) = await self.mood_model.generate_response_async(prompt=prompt) if global_config.debug.show_prompt: logger.info(f"{self.log_prefix} prompt: {prompt}") logger.info(f"{self.log_prefix} response: {response}") logger.info(f"{self.log_prefix} reasoning_content: {reasoning_content}") - + logger.info(f"{self.log_prefix} 情绪状态更新为: {response}") self.mood_state = response @@ -170,15 +168,14 @@ class ChatMood: mood_state=self.mood_state, ) - response, (reasoning_content, model_name) = await self.mood_model.generate_response_async(prompt=prompt) - + if global_config.debug.show_prompt: logger.info(f"{self.log_prefix} prompt: {prompt}") logger.info(f"{self.log_prefix} response: {response}") logger.info(f"{self.log_prefix} reasoning_content: {reasoning_content}") - - logger.info(f"{self.log_prefix} 情绪状态回归为: {response}") + + logger.info(f"{self.log_prefix} 情绪状态回归为: {response}") self.mood_state = response diff --git a/src/plugin_system/apis/chat_api.py b/src/plugin_system/apis/chat_api.py index f436c4ab5..35a210faa 100644 --- a/src/plugin_system/apis/chat_api.py +++ b/src/plugin_system/apis/chat_api.py @@ -39,7 +39,12 @@ class ChatManager: Returns: List[ChatStream]: 聊天流列表 + + Raises: + TypeError: 如果 platform 不是字符串或 SpecialTypes 枚举类型 """ + if not isinstance(platform, (str, SpecialTypes)): + raise TypeError("platform 必须是字符串或是 SpecialTypes 枚举") streams = [] try: for _, stream in get_chat_manager().streams.items(): @@ -60,6 +65,8 @@ class ChatManager: Returns: List[ChatStream]: 群聊聊天流列表 """ + if not isinstance(platform, (str, SpecialTypes)): + raise TypeError("platform 必须是字符串或是 SpecialTypes 枚举") streams = [] try: for _, stream in get_chat_manager().streams.items(): @@ -79,7 +86,12 @@ class ChatManager: Returns: List[ChatStream]: 私聊聊天流列表 + + Raises: + TypeError: 如果 platform 不是字符串或 SpecialTypes 枚举类型 """ + if not isinstance(platform, (str, SpecialTypes)): + raise TypeError("platform 必须是字符串或是 SpecialTypes 枚举") streams = [] try: for _, stream in get_chat_manager().streams.items(): @@ -102,7 +114,17 @@ class ChatManager: Returns: Optional[ChatStream]: 聊天流对象,如果未找到返回None + + Raises: + ValueError: 如果 group_id 为空字符串 + TypeError: 如果 group_id 不是字符串类型或 platform 不是字符串或 SpecialTypes """ + if not isinstance(group_id, str): + raise TypeError("group_id 必须是字符串类型") + if not isinstance(platform, (str, SpecialTypes)): + raise TypeError("platform 必须是字符串或是 SpecialTypes 枚举") + if not group_id: + raise ValueError("group_id 不能为空") try: for _, stream in get_chat_manager().streams.items(): if ( @@ -129,7 +151,17 @@ class ChatManager: Returns: Optional[ChatStream]: 聊天流对象,如果未找到返回None + + Raises: + ValueError: 如果 user_id 为空字符串 + TypeError: 如果 user_id 不是字符串类型或 platform 不是字符串或 SpecialTypes """ + if not isinstance(user_id, str): + raise TypeError("user_id 必须是字符串类型") + if not isinstance(platform, (str, SpecialTypes)): + raise TypeError("platform 必须是字符串或是 SpecialTypes 枚举") + if not user_id: + raise ValueError("user_id 不能为空") try: for _, stream in get_chat_manager().streams.items(): if ( @@ -153,9 +185,15 @@ class ChatManager: Returns: str: 聊天类型 ("group", "private", "unknown") + + Raises: + TypeError: 如果 chat_stream 不是 ChatStream 类型 + ValueError: 如果 chat_stream 为空 """ + if not isinstance(chat_stream, ChatStream): + raise TypeError("chat_stream 必须是 ChatStream 类型") if not chat_stream: - raise ValueError("chat_stream cannot be None") + raise ValueError("chat_stream 不能为 None") if hasattr(chat_stream, "group_info"): return "group" if chat_stream.group_info else "private" @@ -170,9 +208,15 @@ class ChatManager: Returns: Dict[str, Any]: 聊天流信息字典 + + Raises: + TypeError: 如果 chat_stream 不是 ChatStream 类型 + ValueError: 如果 chat_stream 为空 """ if not chat_stream: - return {} + raise ValueError("chat_stream 不能为 None") + if not isinstance(chat_stream, ChatStream): + raise TypeError("chat_stream 必须是 ChatStream 类型") try: info: Dict[str, Any] = { diff --git a/src/plugin_system/apis/emoji_api.py b/src/plugin_system/apis/emoji_api.py index 4f1d03521..cafb52df8 100644 --- a/src/plugin_system/apis/emoji_api.py +++ b/src/plugin_system/apis/emoji_api.py @@ -8,6 +8,8 @@ count = emoji_api.get_count() """ +import random + from typing import Optional, Tuple, List from src.common.logger import get_logger from src.chat.emoji_system.emoji_manager import get_emoji_manager @@ -29,7 +31,15 @@ async def get_by_description(description: str) -> Optional[Tuple[str, str, str]] Returns: Optional[Tuple[str, str, str]]: (base64编码, 表情包描述, 匹配的情感标签) 或 None + + Raises: + ValueError: 如果描述为空字符串 + TypeError: 如果描述不是字符串类型 """ + if not description: + raise ValueError("描述不能为空") + if not isinstance(description, str): + raise TypeError("描述必须是字符串类型") try: logger.debug(f"[EmojiAPI] 根据描述获取表情包: {description}") @@ -55,7 +65,7 @@ async def get_by_description(description: str) -> Optional[Tuple[str, str, str]] return None -async def get_random(count: int = 1) -> Optional[List[Tuple[str, str, str]]]: +async def get_random(count: Optional[int] = 1) -> Optional[List[Tuple[str, str, str]]]: """随机获取指定数量的表情包 Args: @@ -63,8 +73,17 @@ async def get_random(count: int = 1) -> Optional[List[Tuple[str, str, str]]]: Returns: Optional[List[Tuple[str, str, str]]]: 包含(base64编码, 表情包描述, 随机情感标签)的元组列表,如果失败则为None + + Raises: + TypeError: 如果count不是整数类型 + ValueError: 如果count为负数 """ - if count <= 0: + if not isinstance(count, int): + raise TypeError("count 必须是整数类型") + if count < 0: + raise ValueError("count 不能为负数") + if count == 0: + logger.warning("[EmojiAPI] count 为0,返回空列表") return [] try: @@ -90,8 +109,6 @@ async def get_random(count: int = 1) -> Optional[List[Tuple[str, str, str]]]: count = len(valid_emojis) # 随机选择 - import random - selected_emojis = random.sample(valid_emojis, count) results = [] @@ -128,7 +145,15 @@ async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]: Returns: Optional[Tuple[str, str, str]]: (base64编码, 表情包描述, 匹配的情感标签) 或 None + + Raises: + ValueError: 如果情感标签为空字符串 + TypeError: 如果情感标签不是字符串类型 """ + if not emotion: + raise ValueError("情感标签不能为空") + if not isinstance(emotion, str): + raise TypeError("情感标签必须是字符串类型") try: logger.info(f"[EmojiAPI] 根据情感获取表情包: {emotion}") @@ -146,8 +171,6 @@ async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]: return None # 随机选择匹配的表情包 - import random - selected_emoji = random.choice(matching_emojis) emoji_base64 = image_path_to_base64(selected_emoji.full_path) @@ -185,11 +208,11 @@ def get_count() -> int: return 0 -def get_info() -> dict: +def get_info(): """获取表情包系统信息 Returns: - dict: 包含表情包数量、最大数量等信息 + dict: 包含表情包数量、最大数量、可用数量信息 """ try: emoji_manager = get_emoji_manager() @@ -203,7 +226,7 @@ def get_info() -> dict: return {"current_count": 0, "max_count": 0, "available_emojis": 0} -def get_emotions() -> list: +def get_emotions() -> List[str]: """获取所有可用的情感标签 Returns: @@ -223,7 +246,7 @@ def get_emotions() -> list: return [] -def get_descriptions() -> list: +def get_descriptions() -> List[str]: """获取所有表情包描述 Returns: diff --git a/src/plugin_system/apis/generator_api.py b/src/plugin_system/apis/generator_api.py index 6c8cc01da..4763dbd1b 100644 --- a/src/plugin_system/apis/generator_api.py +++ b/src/plugin_system/apis/generator_api.py @@ -5,11 +5,12 @@ 使用方式: from src.plugin_system.apis import generator_api replyer = generator_api.get_replyer(chat_stream) - success, reply_set = await generator_api.generate_reply(chat_stream, action_data, reasoning) + success, reply_set, _ = await generator_api.generate_reply(chat_stream, action_data, reasoning) """ import traceback from typing import Tuple, Any, Dict, List, Optional +from rich.traceback import install from src.common.logger import get_logger from src.chat.replyer.default_generator import DefaultReplyer from src.chat.message_receive.chat_stream import ChatStream @@ -17,6 +18,8 @@ from src.chat.utils.utils import process_llm_response from src.chat.replyer.replyer_manager import replyer_manager from src.plugin_system.base.component_types import ActionInfo +install(extra_lines=3) + logger = get_logger("generator_api") @@ -44,7 +47,12 @@ def get_replyer( Returns: Optional[DefaultReplyer]: 回复器对象,如果获取失败则返回None + + Raises: + ValueError: chat_stream 和 chat_id 均为空 """ + if not chat_id and not chat_stream: + raise ValueError("chat_stream 和 chat_id 不可均为空") try: logger.debug(f"[GeneratorAPI] 正在获取回复器,chat_id: {chat_id}, chat_stream: {'有' if chat_stream else '无'}") return replyer_manager.get_replyer( diff --git a/src/plugin_system/apis/llm_api.py b/src/plugin_system/apis/llm_api.py index 1bcd1f7d2..4c45a38f0 100644 --- a/src/plugin_system/apis/llm_api.py +++ b/src/plugin_system/apis/llm_api.py @@ -14,7 +14,6 @@ from src.config.config import global_config logger = get_logger("llm_api") - # ============================================================================= # LLM模型API函数 # ============================================================================= @@ -31,8 +30,21 @@ def get_available_models() -> Dict[str, Any]: logger.error("[LLMAPI] 无法获取模型列表:全局配置中未找到 model 配置") return {} + # 自动获取所有属性并转换为字典形式 + rets = {} models = global_config.model - return models + attrs = dir(models) + for attr in attrs: + if not attr.startswith("__"): + try: + value = getattr(models, attr) + if not callable(value): # 排除方法 + rets[attr] = value + except Exception as e: + logger.debug(f"[LLMAPI] 获取属性 {attr} 失败: {e}") + continue + return rets + except Exception as e: logger.error(f"[LLMAPI] 获取可用模型失败: {e}") return {} diff --git a/src/plugin_system/apis/send_api.py b/src/plugin_system/apis/send_api.py index a7b4f7de6..de03cd9f8 100644 --- a/src/plugin_system/apis/send_api.py +++ b/src/plugin_system/apis/send_api.py @@ -114,7 +114,11 @@ async def _send_to_target( # 发送消息 sent_msg = await heart_fc_sender.send_message( - bot_message, typing=typing, set_reply=(anchor_message is not None), storage_message=storage_message, show_log=show_log + bot_message, + typing=typing, + set_reply=(anchor_message is not None), + storage_message=storage_message, + show_log=show_log, ) if sent_msg: @@ -363,7 +367,9 @@ async def custom_to_stream( Returns: bool: 是否发送成功 """ - return await _send_to_target(message_type, content, stream_id, display_message, typing, reply_to, storage_message, show_log) + return await _send_to_target( + message_type, content, stream_id, display_message, typing, reply_to, storage_message, show_log + ) async def text_to_group( diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index 83b0abfda..edcee0574 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -75,7 +75,7 @@ class ReplyAction(BaseAction): reply_to = self.action_data.get("reply_to", "") sender, target = self._parse_reply_target(reply_to) - + try: prepared_reply = self.action_data.get("prepared_reply", "") if not prepared_reply: