This commit is contained in:
tt-P607
2025-12-03 17:02:39 +08:00
16 changed files with 597 additions and 65 deletions

View File

@@ -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"

View File

@@ -69,6 +69,9 @@ class PromptBuilder:
# 1. 构建人设块
persona_block = self._build_persona_block()
# 1.5. 构建安全互动准则块
safety_guidelines_block = self._build_safety_guidelines_block()
# 2. 使用 context_builder 获取关系、记忆、工具、表达习惯等
context_data = await self._build_context_data(user_name, chat_stream, user_id)
relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。")
@@ -98,6 +101,7 @@ class PromptBuilder:
PROMPT_NAMES["main"],
user_name=user_name,
persona_block=persona_block,
safety_guidelines_block=safety_guidelines_block,
relation_block=relation_block,
memory_block=memory_block or "(暂无相关记忆)",
tool_info=tool_info or "(暂无工具信息)",
@@ -142,7 +146,10 @@ class PromptBuilder:
# 1. 构建人设块
persona_block = self._build_persona_block()
# 2. 使用 context_builder 获取关系、记忆、工具、表达习惯等
# 1.5. 构建安全互动准则块
safety_guidelines_block = self._build_safety_guidelines_block()
# 2. 使用 context_builder 获取关系、记忆、表达习惯等
context_data = await self._build_context_data(user_name, chat_stream, user_id)
relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。")
memory_block = context_data.get("memory_block", "")
@@ -170,6 +177,7 @@ class PromptBuilder:
PROMPT_NAMES["replyer"],
user_name=user_name,
persona_block=persona_block,
safety_guidelines_block=safety_guidelines_block,
relation_block=relation_block,
memory_block=memory_block or "(暂无相关记忆)",
tool_info=tool_info or "(暂无工具信息)",
@@ -202,6 +210,24 @@ class PromptBuilder:
return "\n\n".join(parts) if parts else "你是一个温暖、真诚的人。"
def _build_safety_guidelines_block(self) -> str:
"""
构建安全互动准则块
从配置中读取 safety_guidelines构建成提示词格式
"""
if global_config is None:
return ""
safety_guidelines = global_config.personality.safety_guidelines
if not safety_guidelines:
return ""
guidelines_text = "\n".join(f"{i + 1}. {line}" for i, line in enumerate(safety_guidelines))
return f"""在任何情况下,你都必须遵守以下由你的设定者为你定义的原则:
{guidelines_text}
如果遇到违反上述原则的请求,请在保持你核心人设的同时,以合适的方式进行回应。"""
def _build_combined_expression_block(self, learned_habits: str) -> str:
"""
构建合并后的表达习惯块
@@ -237,11 +263,13 @@ 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 构建完整的上下文数据
包括:关系信息、记忆、工具、表达习惯等
包括:关系信息、记忆、表达习惯等
"""
if not chat_stream:
return {
@@ -259,12 +287,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,
@@ -284,6 +313,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"],
@@ -505,32 +641,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":
@@ -887,7 +1030,10 @@ class PromptBuilder:
# 1. 构建人设块
persona_block = self._build_persona_block()
# 2. 使用 context_builder 获取关系、记忆、工具、表达习惯等
# 1.5. 构建安全互动准则块
safety_guidelines_block = self._build_safety_guidelines_block()
# 2. 使用 context_builder 获取关系、记忆、表达习惯等
context_data = await self._build_context_data(user_name, chat_stream, user_id)
relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。")
memory_block = context_data.get("memory_block", "")
@@ -916,6 +1062,7 @@ class PromptBuilder:
PROMPT_NAMES["main"],
user_name=user_name,
persona_block=persona_block,
safety_guidelines_block=safety_guidelines_block,
relation_block=relation_block,
memory_block=memory_block or "(暂无相关记忆)",
tool_info=tool_info or "(暂无工具信息)",

View File

@@ -17,6 +17,9 @@ kfc_MAIN_PROMPT = Prompt(
## 人设
{persona_block}
## 安全互动准则
{safety_guidelines_block}
## 你与 {user_name} 的关系
{relation_block}
@@ -88,7 +91,9 @@ kfc_SITUATION_NEW_MESSAGE = Prompt(
name="kfc_situation_new_message",
template="""现在是 {current_time}
{user_name} 刚刚给你发了消息。这是一次新的对话发起(不是对你之前消息的回复)。
{user_name} 刚刚给你发了消息:「{latest_message}
这是一次新的对话发起(不是对你之前消息的回复)。
请决定你要怎么回应。你可以:
- 发送文字消息回复
@@ -103,7 +108,7 @@ kfc_SITUATION_REPLY_IN_TIME = Prompt(
你之前发了消息后一直在等 {user_name} 的回复。
等了大约 {elapsed_minutes:.1f} 分钟(你原本打算最多等 {max_wait_minutes:.1f} 分钟)。
现在 {user_name} 回复了
现在 {user_name} 回复了:「{latest_message}
请决定你接下来要怎么回应。""",
)
@@ -114,7 +119,7 @@ kfc_SITUATION_REPLY_LATE = Prompt(
你之前发了消息后在等 {user_name} 的回复。
你原本打算最多等 {max_wait_minutes:.1f} 分钟,但实际等了 {elapsed_minutes:.1f} 分钟才收到回复。
虽然有点迟,但 {user_name} 终于回复了
虽然有点迟,但 {user_name} 终于回复了:「{latest_message}
请决定你接下来要怎么回应。(可以选择轻轻抱怨一下迟到,也可以装作没在意)""",
)
@@ -275,6 +280,9 @@ kfc_REPLYER_PROMPT = Prompt(
## 人设
{persona_block}
## 安全互动准则
{safety_guidelines_block}
## 你与 {user_name} 的关系
{relation_block}

View File

@@ -176,6 +176,7 @@ class NapcatAdapter(BaseAdapter):
# 消息事件
if post_type == "message":
return await self.message_handler.handle_raw_message(raw) # type: ignore[return-value]
# 通知事件
elif post_type == "notice":
return await self.notice_handler.handle_notice(raw) # type: ignore[return-value]

View File

@@ -60,7 +60,7 @@ class MessageHandler:
user_id = str(sender_info.get("user_id", ""))
# 检查全局封禁用户列表
ban_user_ids = features_config.get("ban_user_id", [])
ban_user_ids = [str(item) for item in features_config.get("ban_user_id", [])]
if user_id in ban_user_ids:
logger.debug(f"用户 {user_id} 在全局封禁列表中,消息被过滤")
return False

View File

@@ -244,6 +244,14 @@ class SetEmojiLikeAction(BaseAction):
async def execute(self) -> tuple[bool, str]:
"""执行设置表情回应的动作"""
# 检查是否在群聊中,该动作仅在群聊中有效
if not self.is_group:
logger.warning("set_emoji_like 动作仅在群聊中有效,当前为私聊场景")
await self.store_action_info(
action_prompt_display="贴表情失败: 该功能仅在群聊中可用", action_done=False
)
return False, "该功能仅在群聊中可用"
message_id = None
set_like = self.action_data.get("set", True)