diff --git a/src/plugins/built_in/kokoro_flow_chatter/chatter.py b/src/plugins/built_in/kokoro_flow_chatter/chatter.py index 08b494133..5b558c75c 100644 --- a/src/plugins/built_in/kokoro_flow_chatter/chatter.py +++ b/src/plugins/built_in/kokoro_flow_chatter/chatter.py @@ -325,6 +325,10 @@ class KokoroFlowChatter(BaseChatter): """ if session.status == SessionStatus.WAITING: # 之前在等待 + # 如果 max_wait_seconds <= 0,说明不是有效的等待状态,视为新消息 + if session.waiting_config.max_wait_seconds <= 0: + return "new_message" + if session.waiting_config.is_timeout(): # 超时了才收到回复 return "reply_late" diff --git a/src/plugins/built_in/kokoro_flow_chatter/context_builder.py b/src/plugins/built_in/kokoro_flow_chatter/context_builder.py index a83a2bf22..c9adf318a 100644 --- a/src/plugins/built_in/kokoro_flow_chatter/context_builder.py +++ b/src/plugins/built_in/kokoro_flow_chatter/context_builder.py @@ -177,7 +177,12 @@ class KFCContextBuilder: return f"你与{sender_name}是普通朋友关系。" async def _build_memory_block(self, chat_history: str, target_message: str) -> str: - """构建记忆块(使用三层记忆系统)""" + """构建记忆块(使用三层记忆系统) + + Args: + chat_history: 聊天历史文本 + target_message: 目标消息/查询文本。如果为空,将使用 chat_history 的前 200 字符作为查询 + """ config = _get_config() if not (config.memory and config.memory.enable): @@ -192,8 +197,18 @@ class KFCContextBuilder: logger.debug("[三层记忆] 管理器未初始化") return "" + # 如果 target_message 为空,使用 chat_history 的前 200 字符作为查询 + query_text = target_message.strip() if target_message else "" + if not query_text and chat_history: + query_text = chat_history[:200].strip() + logger.debug(f"[三层记忆] target_message 为空,使用 chat_history 前 200 字符作为查询") + + if not query_text: + logger.debug("[三层记忆] 没有可用的查询文本,跳过记忆搜索") + return "" + search_result = await unified_manager.search_memories( - query_text=target_message, + query_text=query_text, use_judge=True, recent_chat_history=chat_history, ) diff --git a/src/plugins/built_in/kokoro_flow_chatter/prompt/builder.py b/src/plugins/built_in/kokoro_flow_chatter/prompt/builder.py index ce0e10480..cdd371e63 100644 --- a/src/plugins/built_in/kokoro_flow_chatter/prompt/builder.py +++ b/src/plugins/built_in/kokoro_flow_chatter/prompt/builder.py @@ -73,7 +73,10 @@ class PromptBuilder: safety_guidelines_block = self._build_safety_guidelines_block() # 2. 使用 context_builder 获取关系、记忆、表达习惯等 - context_data = await self._build_context_data(user_name, chat_stream, user_id) + context_data = await self._build_context_data( + user_name, chat_stream, user_id, + session=session, situation_type=situation_type + ) relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。") memory_block = context_data.get("memory_block", "") expression_habits = self._build_combined_expression_block(context_data.get("expression_habits", "")) @@ -148,7 +151,10 @@ class PromptBuilder: safety_guidelines_block = self._build_safety_guidelines_block() # 2. 使用 context_builder 获取关系、记忆、表达习惯等 - context_data = await self._build_context_data(user_name, chat_stream, user_id) + context_data = await self._build_context_data( + user_name, chat_stream, user_id, + session=session, situation_type=situation_type + ) relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。") memory_block = context_data.get("memory_block", "") expression_habits = self._build_combined_expression_block(context_data.get("expression_habits", "")) @@ -259,11 +265,20 @@ class PromptBuilder: user_name: str, chat_stream: Optional["ChatStream"], user_id: Optional[str] = None, + session: Optional[KokoroSession] = None, + situation_type: str = "new_message", ) -> dict[str, str]: """ 使用 KFCContextBuilder 构建完整的上下文数据 包括:关系信息、记忆、表达习惯等 + + Args: + user_name: 用户名称 + chat_stream: 聊天流对象 + user_id: 用户ID + session: KokoroSession 会话对象(用于超时/主动思考场景) + situation_type: 情况类型(用于判断记忆搜索策略) """ if not chat_stream: return { @@ -280,12 +295,13 @@ class PromptBuilder: builder = self._context_builder(chat_stream) - # 获取最近的消息作为 target_message(用于记忆检索) - target_message = "" - if chat_stream.context: - unread = chat_stream.context.get_unread_messages() - if unread: - target_message = unread[-1].processed_plain_text or unread[-1].display_message or "" + # 获取用于记忆检索的查询文本 + target_message = await self._get_memory_search_query( + chat_stream=chat_stream, + session=session, + situation_type=situation_type, + user_name=user_name, + ) context_data = await builder.build_all_context( sender_name=user_name, @@ -304,6 +320,113 @@ class PromptBuilder: "expression_habits": "", } + async def _get_memory_search_query( + self, + chat_stream: Optional["ChatStream"], + session: Optional[KokoroSession], + situation_type: str, + user_name: str, + ) -> str: + """ + 根据场景类型获取合适的记忆搜索查询文本 + + 策略: + 1. 优先使用未读消息(new_message/reply_in_time/reply_late) + 2. 如果没有未读消息(timeout/proactive),使用最近的历史消息 + 3. 如果历史消息也为空,从 session 的 mental_log 中提取 + 4. 最后回退到用户名作为查询 + + Args: + chat_stream: 聊天流对象 + session: KokoroSession 会话对象 + situation_type: 情况类型 + user_name: 用户名称 + + Returns: + 用于记忆搜索的查询文本 + """ + target_message = "" + + # 策略1: 优先从未读消息获取(适用于 new_message/reply_in_time/reply_late) + if chat_stream and chat_stream.context: + unread = chat_stream.context.get_unread_messages() + if unread: + target_message = unread[-1].processed_plain_text or unread[-1].display_message or "" + if target_message: + logger.debug(f"[记忆搜索] 使用未读消息作为查询: {target_message[:50]}...") + return target_message + + # 策略2: 从最近的历史消息获取(适用于 timeout/proactive) + if chat_stream and chat_stream.context: + history_messages = chat_stream.context.history_messages + if history_messages: + # 获取最近的几条非通知消息,组合成查询 + recent_texts = [] + for msg in reversed(history_messages[-5:]): + content = getattr(msg, "processed_plain_text", "") or getattr(msg, "display_message", "") + if content and not getattr(msg, "is_notify", False): + recent_texts.append(content) + if len(recent_texts) >= 3: + break + + if recent_texts: + target_message = " ".join(reversed(recent_texts)) + logger.debug(f"[记忆搜索] 使用历史消息作为查询 (situation={situation_type}): {target_message[:80]}...") + return target_message + + # 策略3: 从 session 的 mental_log 中提取(超时/主动思考场景的最后手段) + if session and situation_type in ("timeout", "proactive"): + entries = session.get_recent_entries(limit=10) + recent_texts = [] + + for entry in reversed(entries): + # 从用户消息中提取 + if entry.event_type == EventType.USER_MESSAGE and entry.content: + recent_texts.append(entry.content) + # 从 bot 的预期反应中提取(可能包含相关话题) + elif entry.event_type == EventType.BOT_PLANNING and entry.expected_reaction: + recent_texts.append(entry.expected_reaction) + + if len(recent_texts) >= 3: + break + + if recent_texts: + target_message = " ".join(reversed(recent_texts)) + logger.debug(f"[记忆搜索] 使用 mental_log 作为查询 (situation={situation_type}): {target_message[:80]}...") + return target_message + + # 策略4: 最后回退 - 使用用户名 + 场景描述 + if situation_type == "timeout": + target_message = f"与 {user_name} 的对话 等待回复" + elif situation_type == "proactive": + target_message = f"与 {user_name} 的对话 主动发起聊天" + else: + target_message = f"与 {user_name} 的对话" + + logger.debug(f"[记忆搜索] 使用回退查询 (situation={situation_type}): {target_message}") + return target_message + + def _get_latest_user_message(self, session: Optional[KokoroSession]) -> str: + """ + 获取最新的用户消息内容 + + Args: + session: KokoroSession 会话对象 + + Returns: + 最新用户消息的内容,如果没有则返回提示文本 + """ + if not session: + return "(未知消息)" + + # 从 mental_log 中获取最新的用户消息 + entries = session.get_recent_entries(limit=10) + for entry in reversed(entries): + if entry.event_type == EventType.USER_MESSAGE and entry.content: + return entry.content + + return "(消息内容不可用)" + async def _build_chat_history_block( self, chat_stream: Optional["ChatStream"], @@ -525,32 +648,39 @@ class PromptBuilder: situation_type = "new_message" if situation_type == "new_message": + # 获取最新消息内容 + latest_message = self._get_latest_user_message(session) return await global_prompt_manager.format_prompt( PROMPT_NAMES["situation_new_message"], current_time=current_time, user_name=user_name, + latest_message=latest_message, ) elif situation_type == "reply_in_time": elapsed = session.waiting_config.get_elapsed_seconds() max_wait = session.waiting_config.max_wait_seconds + latest_message = self._get_latest_user_message(session) return await global_prompt_manager.format_prompt( PROMPT_NAMES["situation_reply_in_time"], current_time=current_time, user_name=user_name, elapsed_minutes=elapsed / 60, max_wait_minutes=max_wait / 60, + latest_message=latest_message, ) elif situation_type == "reply_late": elapsed = session.waiting_config.get_elapsed_seconds() max_wait = session.waiting_config.max_wait_seconds + latest_message = self._get_latest_user_message(session) return await global_prompt_manager.format_prompt( PROMPT_NAMES["situation_reply_late"], current_time=current_time, user_name=user_name, elapsed_minutes=elapsed / 60, max_wait_minutes=max_wait / 60, + latest_message=latest_message, ) elif situation_type == "timeout": @@ -911,7 +1041,10 @@ class PromptBuilder: safety_guidelines_block = self._build_safety_guidelines_block() # 2. 使用 context_builder 获取关系、记忆、表达习惯等 - context_data = await self._build_context_data(user_name, chat_stream, user_id) + context_data = await self._build_context_data( + user_name, chat_stream, user_id, + session=session, situation_type=situation_type + ) relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。") memory_block = context_data.get("memory_block", "") expression_habits = self._build_combined_expression_block(context_data.get("expression_habits", "")) diff --git a/src/plugins/built_in/kokoro_flow_chatter/prompt/prompts.py b/src/plugins/built_in/kokoro_flow_chatter/prompt/prompts.py index 6ac97718c..d5c8c176d 100644 --- a/src/plugins/built_in/kokoro_flow_chatter/prompt/prompts.py +++ b/src/plugins/built_in/kokoro_flow_chatter/prompt/prompts.py @@ -88,7 +88,9 @@ kfc_SITUATION_NEW_MESSAGE = Prompt( name="kfc_situation_new_message", template="""现在是 {current_time}。 -{user_name} 刚刚给你发了消息。这是一次新的对话发起(不是对你之前消息的回复)。 +{user_name} 刚刚给你发了消息:「{latest_message}」 + +这是一次新的对话发起(不是对你之前消息的回复)。 请决定你要怎么回应。你可以: - 发送文字消息回复 @@ -103,7 +105,7 @@ kfc_SITUATION_REPLY_IN_TIME = Prompt( 你之前发了消息后一直在等 {user_name} 的回复。 等了大约 {elapsed_minutes:.1f} 分钟(你原本打算最多等 {max_wait_minutes:.1f} 分钟)。 -现在 {user_name} 回复了! +现在 {user_name} 回复了:「{latest_message}」 请决定你接下来要怎么回应。""", ) @@ -114,7 +116,7 @@ kfc_SITUATION_REPLY_LATE = Prompt( 你之前发了消息后在等 {user_name} 的回复。 你原本打算最多等 {max_wait_minutes:.1f} 分钟,但实际等了 {elapsed_minutes:.1f} 分钟才收到回复。 -虽然有点迟,但 {user_name} 终于回复了。 +虽然有点迟,但 {user_name} 终于回复了:「{latest_message}」 请决定你接下来要怎么回应。(可以选择轻轻抱怨一下迟到,也可以装作没在意)""", )