feat: 优化主动思考器和提示词构建逻辑,新增用户关系和等待思考提示词模板
This commit is contained in:
@@ -22,7 +22,7 @@ from src.plugin_system.apis.unified_scheduler import TriggerType, unified_schedu
|
|||||||
|
|
||||||
from .models import EventType, SessionStatus
|
from .models import EventType, SessionStatus
|
||||||
from .planner import generate_plan
|
from .planner import generate_plan
|
||||||
from .replyer import generate_reply_text
|
from .replyer import _clean_reply_text, generate_reply_text
|
||||||
from .session import KokoroSession, get_session_manager
|
from .session import KokoroSession, get_session_manager
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -211,13 +211,16 @@ class ProactiveThinker:
|
|||||||
"""处理连续思考"""
|
"""处理连续思考"""
|
||||||
self._stats["continuous_thinking_triggered"] += 1
|
self._stats["continuous_thinking_triggered"] += 1
|
||||||
|
|
||||||
# 生成等待中的想法
|
# 获取用户名
|
||||||
thought = self._generate_waiting_thought(session, progress)
|
user_name = await self._get_user_name(session.user_id, session.stream_id)
|
||||||
|
|
||||||
|
# 调用 LLM 生成等待中的想法
|
||||||
|
thought = await self._generate_waiting_thought(session, user_name, progress)
|
||||||
|
|
||||||
# 记录到 mental_log
|
# 记录到 mental_log
|
||||||
session.add_waiting_update(
|
session.add_waiting_update(
|
||||||
waiting_thought=thought,
|
waiting_thought=thought,
|
||||||
mood="", # 可以根据进度设置心情
|
mood="", # 心情已融入 thought 中
|
||||||
)
|
)
|
||||||
|
|
||||||
# 更新思考计数
|
# 更新思考计数
|
||||||
@@ -232,10 +235,104 @@ class ProactiveThinker:
|
|||||||
f"progress={progress:.1%}, thought={thought[:30]}..."
|
f"progress={progress:.1%}, thought={thought[:30]}..."
|
||||||
)
|
)
|
||||||
|
|
||||||
def _generate_waiting_thought(self, session: KokoroSession, progress: float) -> str:
|
async def _generate_waiting_thought(
|
||||||
"""生成等待中的想法"""
|
self,
|
||||||
elapsed_minutes = session.waiting_config.get_elapsed_minutes()
|
session: KokoroSession,
|
||||||
|
user_name: str,
|
||||||
|
progress: float,
|
||||||
|
) -> str:
|
||||||
|
"""调用 LLM 生成等待中的想法"""
|
||||||
|
try:
|
||||||
|
from src.chat.utils.prompt import global_prompt_manager
|
||||||
|
from src.plugin_system.apis import llm_api
|
||||||
|
|
||||||
|
from .prompt.builder import get_prompt_builder
|
||||||
|
from .prompt.prompts import PROMPT_NAMES
|
||||||
|
|
||||||
|
# 使用 PromptBuilder 构建人设块
|
||||||
|
prompt_builder = get_prompt_builder()
|
||||||
|
persona_block = prompt_builder._build_persona_block()
|
||||||
|
|
||||||
|
# 获取关系信息
|
||||||
|
relation_block = f"你与 {user_name} 还不太熟悉。"
|
||||||
|
try:
|
||||||
|
from src.person_info.relationship_manager import relationship_manager
|
||||||
|
|
||||||
|
person_info_manager = await self._get_person_info_manager()
|
||||||
|
if person_info_manager:
|
||||||
|
platform = global_config.bot.platform if global_config else "qq"
|
||||||
|
person_id = person_info_manager.get_person_id(platform, session.user_id)
|
||||||
|
relationship = await relationship_manager.get_relationship(person_id)
|
||||||
|
if relationship:
|
||||||
|
relation_block = f"你与 {user_name} 的亲密度是 {relationship.intimacy}。{relationship.description or ''}"
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"获取关系信息失败: {e}")
|
||||||
|
|
||||||
|
# 获取上次发送的消息
|
||||||
|
last_bot_message = "(未知)"
|
||||||
|
for entry in reversed(session.mental_log):
|
||||||
|
if entry.event_type == EventType.BOT_PLANNING and entry.actions:
|
||||||
|
for action in entry.actions:
|
||||||
|
if action.get("type") == "kfc_reply":
|
||||||
|
content = action.get("content", "")
|
||||||
|
if content:
|
||||||
|
last_bot_message = content[:100] + ("..." if len(content) > 100 else "")
|
||||||
|
break
|
||||||
|
if last_bot_message != "(未知)":
|
||||||
|
break
|
||||||
|
|
||||||
|
# 构建提示词
|
||||||
|
elapsed_minutes = session.waiting_config.get_elapsed_minutes()
|
||||||
|
max_wait_minutes = session.waiting_config.max_wait_seconds / 60
|
||||||
|
expected_reaction = session.waiting_config.expected_reaction or "对方能回复点什么"
|
||||||
|
|
||||||
|
prompt = await global_prompt_manager.format_prompt(
|
||||||
|
PROMPT_NAMES["waiting_thought"],
|
||||||
|
persona_block=persona_block,
|
||||||
|
user_name=user_name,
|
||||||
|
relation_block=relation_block,
|
||||||
|
last_bot_message=last_bot_message,
|
||||||
|
expected_reaction=expected_reaction,
|
||||||
|
elapsed_minutes=elapsed_minutes,
|
||||||
|
max_wait_minutes=max_wait_minutes,
|
||||||
|
progress_percent=int(progress * 100),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 调用情绪模型
|
||||||
|
models = llm_api.get_available_models()
|
||||||
|
emotion_config = models.get("emotion") or models.get("replyer")
|
||||||
|
|
||||||
|
if not emotion_config:
|
||||||
|
logger.warning("[ProactiveThinker] 未找到 emotion/replyer 模型配置,使用默认想法")
|
||||||
|
return self._get_fallback_thought(elapsed_minutes, progress)
|
||||||
|
|
||||||
|
success, raw_response, _, model_name = await llm_api.generate_with_model(
|
||||||
|
prompt=prompt,
|
||||||
|
model_config=emotion_config,
|
||||||
|
request_type="kokoro_flow_chatter.waiting_thought",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not success or not raw_response:
|
||||||
|
logger.warning(f"[ProactiveThinker] LLM 调用失败: {raw_response}")
|
||||||
|
return self._get_fallback_thought(elapsed_minutes, progress)
|
||||||
|
|
||||||
|
# 使用统一的文本清理函数
|
||||||
|
thought = _clean_reply_text(raw_response)
|
||||||
|
|
||||||
|
logger.debug(f"[ProactiveThinker] LLM 生成等待想法 (model={model_name}): {thought[:50]}...")
|
||||||
|
return thought
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[ProactiveThinker] 生成等待想法失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return self._get_fallback_thought(
|
||||||
|
session.waiting_config.get_elapsed_minutes(),
|
||||||
|
progress
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_fallback_thought(self, elapsed_minutes: float, progress: float) -> str:
|
||||||
|
"""获取备用的等待想法(当 LLM 调用失败时使用)"""
|
||||||
if progress < 0.4:
|
if progress < 0.4:
|
||||||
thoughts = [
|
thoughts = [
|
||||||
f"已经等了 {elapsed_minutes:.0f} 分钟了,对方可能在忙吧...",
|
f"已经等了 {elapsed_minutes:.0f} 分钟了,对方可能在忙吧...",
|
||||||
@@ -254,9 +351,16 @@ class ProactiveThinker:
|
|||||||
"要不要主动说点什么呢...",
|
"要不要主动说点什么呢...",
|
||||||
"快到时间了,对方还是没回",
|
"快到时间了,对方还是没回",
|
||||||
]
|
]
|
||||||
|
|
||||||
return random.choice(thoughts)
|
return random.choice(thoughts)
|
||||||
|
|
||||||
|
async def _get_person_info_manager(self):
|
||||||
|
"""获取 person_info_manager"""
|
||||||
|
try:
|
||||||
|
from src.person_info.person_info import get_person_info_manager
|
||||||
|
return get_person_info_manager()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
async def _handle_timeout(self, session: KokoroSession) -> None:
|
async def _handle_timeout(self, session: KokoroSession) -> None:
|
||||||
"""处理等待超时"""
|
"""处理等待超时"""
|
||||||
self._stats["timeout_decisions"] += 1
|
self._stats["timeout_decisions"] += 1
|
||||||
@@ -577,10 +681,7 @@ class ProactiveThinker:
|
|||||||
from src.person_info.person_info import get_person_info_manager
|
from src.person_info.person_info import get_person_info_manager
|
||||||
|
|
||||||
person_info_manager = get_person_info_manager()
|
person_info_manager = get_person_info_manager()
|
||||||
# 从 stream_id 提取 platform(格式通常是 platform_xxx)
|
platform = global_config.bot.platform if global_config else "qq"
|
||||||
platform = "qq" # 默认平台
|
|
||||||
if "_" in stream_id:
|
|
||||||
platform = stream_id.split("_")[0]
|
|
||||||
|
|
||||||
person_id = person_info_manager.get_person_id(platform, user_id)
|
person_id = person_info_manager.get_person_id(platform, user_id)
|
||||||
person_name = await person_info_manager.get_value(person_id, "person_name")
|
person_name = await person_info_manager.get_value(person_id, "person_name")
|
||||||
|
|||||||
@@ -149,8 +149,8 @@ class PromptBuilder:
|
|||||||
# 3. 构建活动流
|
# 3. 构建活动流
|
||||||
activity_stream = await self._build_activity_stream(session, user_name)
|
activity_stream = await self._build_activity_stream(session, user_name)
|
||||||
|
|
||||||
# 4. 构建当前情况(简化版,不需要那么详细)
|
# 4. 构建当前情况(回复器专用,简化版,不包含决策语言)
|
||||||
current_situation = await self._build_current_situation(
|
current_situation = await self._build_replyer_situation(
|
||||||
session, user_name, situation_type, extra_context
|
session, user_name, situation_type, extra_context
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -315,9 +315,15 @@ class PromptBuilder:
|
|||||||
if not history_messages:
|
if not history_messages:
|
||||||
return "(暂无聊天记录)"
|
return "(暂无聊天记录)"
|
||||||
|
|
||||||
|
# 过滤非文本消息(如戳一戳、禁言等系统通知)
|
||||||
|
text_messages = self._filter_text_messages(history_messages)
|
||||||
|
|
||||||
|
if not text_messages:
|
||||||
|
return "(暂无聊天记录)"
|
||||||
|
|
||||||
# 构建可读消息
|
# 构建可读消息
|
||||||
chat_content, _ = await build_readable_messages_with_id(
|
chat_content, _ = await build_readable_messages_with_id(
|
||||||
messages=[msg.flatten() for msg in history_messages[-30:]], # 最多30条
|
messages=[msg.flatten() for msg in text_messages[-30:]], # 最多30条
|
||||||
timestamp_mode="normal_no_YMD",
|
timestamp_mode="normal_no_YMD",
|
||||||
truncate=False,
|
truncate=False,
|
||||||
show_actions=False,
|
show_actions=False,
|
||||||
@@ -329,6 +335,33 @@ class PromptBuilder:
|
|||||||
logger.warning(f"构建聊天历史块失败: {e}")
|
logger.warning(f"构建聊天历史块失败: {e}")
|
||||||
return "(获取聊天记录失败)"
|
return "(获取聊天记录失败)"
|
||||||
|
|
||||||
|
def _filter_text_messages(self, messages: list) -> list:
|
||||||
|
"""
|
||||||
|
过滤非文本消息
|
||||||
|
|
||||||
|
移除系统通知消息(如戳一戳、禁言等),只保留正常的文本聊天消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
messages: 消息列表(DatabaseMessages 对象)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
过滤后的消息列表
|
||||||
|
"""
|
||||||
|
filtered = []
|
||||||
|
for msg in messages:
|
||||||
|
# 跳过系统通知消息(戳一戳、禁言等)
|
||||||
|
if getattr(msg, "is_notify", False):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 跳过没有实际文本内容的消息
|
||||||
|
content = getattr(msg, "processed_plain_text", "") or getattr(msg, "display_message", "")
|
||||||
|
if not content or not content.strip():
|
||||||
|
continue
|
||||||
|
|
||||||
|
filtered.append(msg)
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
|
||||||
async def _build_activity_stream(
|
async def _build_activity_stream(
|
||||||
self,
|
self,
|
||||||
session: KokoroSession,
|
session: KokoroSession,
|
||||||
@@ -668,6 +701,66 @@ class PromptBuilder:
|
|||||||
|
|
||||||
注意:kfc_reply 动作不需要填写 content 字段,回复内容会单独生成。"""
|
注意:kfc_reply 动作不需要填写 content 字段,回复内容会单独生成。"""
|
||||||
|
|
||||||
|
async def _build_replyer_situation(
|
||||||
|
self,
|
||||||
|
session: KokoroSession,
|
||||||
|
user_name: str,
|
||||||
|
situation_type: str,
|
||||||
|
extra_context: dict,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
构建回复器专用的当前情况描述
|
||||||
|
|
||||||
|
与 Planner 的 _build_current_situation 不同,这里不包含决策性语言,
|
||||||
|
只描述当前的情景背景
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M")
|
||||||
|
|
||||||
|
if situation_type == "new_message":
|
||||||
|
return f"现在是 {current_time}。{user_name} 刚给你发了消息。"
|
||||||
|
|
||||||
|
elif situation_type == "reply_in_time":
|
||||||
|
elapsed = session.waiting_config.get_elapsed_seconds()
|
||||||
|
max_wait = session.waiting_config.max_wait_seconds
|
||||||
|
return (
|
||||||
|
f"现在是 {current_time}。\n"
|
||||||
|
f"你之前发了消息后在等 {user_name} 的回复。"
|
||||||
|
f"等了大约 {elapsed / 60:.1f} 分钟(你原本打算最多等 {max_wait / 60:.1f} 分钟)。"
|
||||||
|
f"现在 {user_name} 回复了!"
|
||||||
|
)
|
||||||
|
|
||||||
|
elif situation_type == "reply_late":
|
||||||
|
elapsed = session.waiting_config.get_elapsed_seconds()
|
||||||
|
max_wait = session.waiting_config.max_wait_seconds
|
||||||
|
return (
|
||||||
|
f"现在是 {current_time}。\n"
|
||||||
|
f"你之前发了消息后在等 {user_name} 的回复。"
|
||||||
|
f"你原本打算最多等 {max_wait / 60:.1f} 分钟,但实际等了 {elapsed / 60:.1f} 分钟才收到回复。"
|
||||||
|
f"虽然有点迟,但 {user_name} 终于回复了。"
|
||||||
|
)
|
||||||
|
|
||||||
|
elif situation_type == "timeout":
|
||||||
|
elapsed = session.waiting_config.get_elapsed_seconds()
|
||||||
|
max_wait = session.waiting_config.max_wait_seconds
|
||||||
|
return (
|
||||||
|
f"现在是 {current_time}。\n"
|
||||||
|
f"你之前发了消息后一直在等 {user_name} 的回复。"
|
||||||
|
f"你原本打算最多等 {max_wait / 60:.1f} 分钟,现在已经等了 {elapsed / 60:.1f} 分钟了,对方还是没回。"
|
||||||
|
f"你决定主动说点什么。"
|
||||||
|
)
|
||||||
|
|
||||||
|
elif situation_type == "proactive":
|
||||||
|
silence = extra_context.get("silence_duration", "一段时间")
|
||||||
|
return (
|
||||||
|
f"现在是 {current_time}。\n"
|
||||||
|
f"你和 {user_name} 已经有一段时间没聊天了(沉默了 {silence})。"
|
||||||
|
f"你决定主动找 {user_name} 聊点什么。"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 默认
|
||||||
|
return f"现在是 {current_time}。"
|
||||||
|
|
||||||
async def _build_reply_context(
|
async def _build_reply_context(
|
||||||
self,
|
self,
|
||||||
session: KokoroSession,
|
session: KokoroSession,
|
||||||
|
|||||||
@@ -337,6 +337,45 @@ kfc_REPLYER_CONTEXT_PROACTIVE = Prompt(
|
|||||||
你决定主动打破沉默,找 {user_name} 聊点什么。想一个自然的开场白,不要太突兀。""",
|
你决定主动打破沉默,找 {user_name} 聊点什么。想一个自然的开场白,不要太突兀。""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# =================================================================================================
|
||||||
|
# 等待思考提示词模板(用于生成等待中的心理活动)
|
||||||
|
# =================================================================================================
|
||||||
|
|
||||||
|
kfc_WAITING_THOUGHT = Prompt(
|
||||||
|
name="kfc_waiting_thought",
|
||||||
|
template="""# 等待中的心理活动
|
||||||
|
|
||||||
|
## 你是谁
|
||||||
|
{persona_block}
|
||||||
|
|
||||||
|
## 你与 {user_name} 的关系
|
||||||
|
{relation_block}
|
||||||
|
|
||||||
|
## 当前情况
|
||||||
|
你刚才给 {user_name} 发了消息,现在正在等待对方回复。
|
||||||
|
|
||||||
|
**你发的消息**:{last_bot_message}
|
||||||
|
**你期待的反应**:{expected_reaction}
|
||||||
|
**已等待时间**:{elapsed_minutes:.1f} 分钟
|
||||||
|
**计划最多等待**:{max_wait_minutes:.1f} 分钟
|
||||||
|
**等待进度**:{progress_percent}%
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
请描述你此刻等待时的内心想法。这是你私下的心理活动,不是要发送的消息。
|
||||||
|
|
||||||
|
**要求**:
|
||||||
|
- 用第一人称描述你的感受和想法
|
||||||
|
- 要符合你的性格和你们的关系
|
||||||
|
- 根据等待进度自然表达情绪变化:
|
||||||
|
- 初期(0-40%):可能比较平静,稍微期待
|
||||||
|
- 中期(40-70%):可能开始有点在意,但还好
|
||||||
|
- 后期(70-100%):可能有点焦虑、担心,或者想主动做点什么
|
||||||
|
- 不要太长,1-2句话即可
|
||||||
|
- 不要输出 JSON,直接输出你的想法
|
||||||
|
|
||||||
|
现在,请直接输出你等待时的内心想法:""",
|
||||||
|
)
|
||||||
|
|
||||||
# 导出所有模板名称,方便外部引用
|
# 导出所有模板名称,方便外部引用
|
||||||
PROMPT_NAMES = {
|
PROMPT_NAMES = {
|
||||||
"main": "kfc_main",
|
"main": "kfc_main",
|
||||||
@@ -347,6 +386,7 @@ PROMPT_NAMES = {
|
|||||||
"replyer_context_in_time": "kfc_replyer_context_in_time",
|
"replyer_context_in_time": "kfc_replyer_context_in_time",
|
||||||
"replyer_context_late": "kfc_replyer_context_late",
|
"replyer_context_late": "kfc_replyer_context_late",
|
||||||
"replyer_context_proactive": "kfc_replyer_context_proactive",
|
"replyer_context_proactive": "kfc_replyer_context_proactive",
|
||||||
|
"waiting_thought": "kfc_waiting_thought",
|
||||||
"situation_new_message": "kfc_situation_new_message",
|
"situation_new_message": "kfc_situation_new_message",
|
||||||
"situation_reply_in_time": "kfc_situation_reply_in_time",
|
"situation_reply_in_time": "kfc_situation_reply_in_time",
|
||||||
"situation_reply_late": "kfc_situation_reply_late",
|
"situation_reply_late": "kfc_situation_reply_late",
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ class PokeAction(BaseAction):
|
|||||||
for i in range(times):
|
for i in range(times):
|
||||||
logger.info(f"正在向 {display_name} ({user_id}) 发送第 {i + 1}/{times} 次戳一戳...")
|
logger.info(f"正在向 {display_name} ({user_id}) 发送第 {i + 1}/{times} 次戳一戳...")
|
||||||
await self.send_command(
|
await self.send_command(
|
||||||
"SEND_POKE", args=poke_args, display_message=f"戳了戳 {display_name} ({i + 1}/{times})"
|
"SEND_POKE", args=poke_args, display_message=f"(系统动作)戳了戳 {display_name} ({i + 1}/{times})"
|
||||||
)
|
)
|
||||||
# 添加一个延迟,避免因发送过快导致后续戳一戳失败
|
# 添加一个延迟,避免因发送过快导致后续戳一戳失败
|
||||||
await asyncio.sleep(1.5)
|
await asyncio.sleep(1.5)
|
||||||
|
|||||||
Reference in New Issue
Block a user