This commit is contained in:
明天好像没什么
2025-10-31 22:43:34 +08:00
10 changed files with 265 additions and 221 deletions

View File

@@ -185,15 +185,24 @@ class ChatterPlanExecutor:
"reply_content": "",
}
# 构建回复动作参数
action_data = action_info.action_data or {}
# 如果action_info中有should_quote_reply且action_data中没有则添加到action_data中
if action_info.should_quote_reply is not None and "should_quote_reply" not in action_data:
action_data["should_quote_reply"] = action_info.should_quote_reply
action_params = {
"chat_id": plan.chat_id,
"target_message": action_info.action_message,
"reasoning": action_info.reasoning,
"action_data": action_info.action_data or {},
"action_data": action_data,
"clear_unread_messages": clear_unread,
}
logger.debug(f"📬 [PlanExecutor] 准备调用 ActionManagertarget_message: {action_info.action_message}")
logger.debug(
f"📬 [PlanExecutor] 准备调用 ActionManagertarget_message: {action_info.action_message}, "
f"should_quote_reply: {action_info.should_quote_reply}"
)
# 通过动作管理器执行回复
execution_result = await self.action_manager.execute_action(

View File

@@ -601,6 +601,14 @@ class ChatterPlanFilter:
reasoning = f"LLM 返回了当前不可用的动作 '{action}'。原始理由: {reasoning}"
action = "no_action"
# 从action_data中提取should_quote_reply参数
should_quote_reply = action_data.get("should_quote_reply", None)
# 将should_quote_reply转换为布尔值如果是字符串的话
if isinstance(should_quote_reply, str):
should_quote_reply = should_quote_reply.lower() in ["true", "1", "yes"]
elif not isinstance(should_quote_reply, bool):
should_quote_reply = None
parsed_actions.append(
ActionPlannerInfo(
action_type=action,
@@ -608,6 +616,7 @@ class ChatterPlanFilter:
action_data=action_data,
action_message=action_message_obj, # 使用转换后的 DatabaseMessages 对象
available_actions=plan.available_actions,
should_quote_reply=should_quote_reply, # 传递should_quote_reply参数
)
)
except Exception as e:

View File

@@ -318,6 +318,7 @@ class ChatterActionPlanner:
reasoning="Normal模式: 兴趣度达到阈值,直接回复",
action_data={"target_message_id": target_message.message_id},
action_message=target_message,
should_quote_reply=False, # Normal模式默认不引用回复保持对话流畅
)
# Normal模式下直接构建最小化的Plan跳过generator和action_modifier

View File

@@ -81,38 +81,56 @@ def init_prompts():
"reasoning": "选择该动作的理由",
"action_data": {{
"target_message_id": "m123",
"content": "你的回复内容"
"content": "你的回复内容",
"should_quote_reply": false
}}
}}
]
}}
```
示例(多重回复,并行):
示例(多重回复,并行 - 需要区分回复对象时才引用:
```json
{{
"thinking": "在这里写下你的思绪流...",
"actions": [
{{
"action_type": "reply",
"reasoning": "理由A",
"reasoning": "理由A - 这个消息较早且需要明确回复对象",
"action_data": {{
"target_message_id": "m124",
"content": "对A的回复"
"content": "对A的回复",
"should_quote_reply": true
}}
}},
{{
"action_type": "reply",
"reasoning": "理由B",
"reasoning": "理由B - 这是对最新消息的自然接续",
"action_data": {{
"target_message_id": "m125",
"content": "对B的回复"
"content": "对B的回复",
"should_quote_reply": false
}}
}}
]
}}
```
# 引用回复控制should_quote_reply
在群聊中回复消息时,你可以通过 `should_quote_reply` 参数控制是否引用原消息:
- **true**: 明确引用原消息(适用于需要明确指向特定消息时,如回答问题、回应多人之一、回复较早的消息)
- **false**: 不引用原消息(适用于自然对话流、接续最新话题、轻松闲聊等场景)
- **不填写**: 系统将自动决定(默认不引用,让对话更流畅)
**【重要】默认策略:大多数情况下应该使用 `false` 以保持对话自然流畅**
**使用建议**
- 当对话自然流畅、你的回复是接续最新话题时,**建议明确设为 `false`** 以避免打断对话节奏
- 当需要明确回复某个特定用户或特定问题时,设为 `true` 以帮助定位
- 当群聊中多人同时发言,你要回复其中一个较早的消息(非最新消息)时,设为 `true`
- 当有人直接@你或明确向你提问时,可以考虑设为 `true` 表明你在回复他
- 私聊场景**必须**设为 `false` 或不填(因为只有两个人,引用是多余的)
# 强制规则
- 需要目标消息的动作reply/poke_user/set_emoji_like 等),必须提供准确的 target_message_id来自未读历史里的 <m...> 标签)。
- 当动作需要额外参数时,必须在 action_data 中补全。

View File

@@ -10,6 +10,7 @@ from typing import Any, Literal
from sqlalchemy import select
from src.chat.express.expression_selector import expression_selector
from src.chat.utils.prompt import Prompt
from src.common.database.sqlalchemy_database_api import get_db_session
from src.common.database.sqlalchemy_models import ChatStreams
from src.common.logger import get_logger
@@ -20,6 +21,133 @@ from src.plugin_system.apis import message_api, send_api
logger = get_logger("proactive_thinking_executor")
# ==================================================================================================
# == Prompt Templates
# ==================================================================================================
# 决策 Prompt
decision_prompt_template = Prompt(
"""你的人设是:
{bot_personality}
现在是 {current_time},你正在考虑是否要在与 "{stream_name}" 的对话中主动说些什么。
【你当前的心情】
{current_mood}
【聊天环境信息】
- 整体印象: {stream_impression}
- 聊天风格: {chat_style}
- 常见话题: {topic_keywords}
- 你的兴趣程度: {interest_score:.2f}/1.0
{last_decision_text}
【最近的聊天记录】
{recent_chat_history}
请根据以上信息,决定你现在应该做什么:
**选项1什么都不做 (do_nothing)**
- 适用场景:气氛不适合说话、最近对话很活跃、没什么特别想说的、或者此时说话会显得突兀。
- 心情影响:如果心情不好(如生气、难过),可能更倾向于保持沉默。
**选项2简单冒个泡 (simple_bubble)**
- 适用场景:对话有些冷清,你想缓和气氛或开启新的互动。
- 方式:说一句轻松随意的话,旨在建立或维持连接。
- 心情影响:心情会影响你冒泡的方式和内容。
**选项3发起一次有目的的互动 (throw_topic)**
- 适用场景:你想延续对话、表达关心、或深入讨论某个具体话题。
- **【互动类型1延续约定或提醒】(最高优先级)**:检查最近的聊天记录,是否存在可以延续的互动。例如,如果昨晚的最后一条消息是“晚安”,现在是早上,一个“早安”的回应是绝佳的选择。如果之前提到过某个约定(如“待会聊”),现在可以主动跟进。
- **【互动类型2展现真诚的关心】(次高优先级)**:如果不存在可延续的约定,请仔细阅读聊天记录,寻找对方提及的个人状况(如天气、出行、身体、情绪、工作学习等),并主动表达关心。
- **【互动类型3开启新话题】**:当以上两点都不适用时,可以考虑开启一个你感兴趣的新话题。
- 心情影响:心情会影响你想发起互动的方式和内容。
请以JSON格式回复你的决策
{{
"action": "do_nothing" | "simple_bubble" | "throw_topic",
"reasoning": "你的决策理由(请结合你的心情、聊天环境和对话历史进行分析)",
"topic": "(仅当action=throw_topic时填写)你的互动意图(如:回应晚安并说早安、关心对方的考试情况、讨论新游戏)"
}}
注意:
1. 兴趣度较低(<0.4)时或者最近聊天很活跃不到1小时倾向于 `do_nothing` 或 `simple_bubble`。
2. 你的心情会影响你的行动倾向和表达方式。
3. 参考上次决策,避免重复,并可根据上次的互动效果调整策略。
4. 只有在真的有感而发时才选择 `throw_topic`。
5. 保持你的人设,确保行为一致性。
""",
name="proactive_thinking_decision",
)
# 冒泡回复 Prompt
simple_bubble_reply_prompt_template = Prompt(
"""你的人设是:
{bot_personality}
距离上次对话已经有一段时间了,你决定主动说些什么,轻松地开启新的互动。
【你当前的心情】
{current_mood}
【聊天环境】
- 整体印象: {stream_impression}
- 聊天风格: {chat_style}
【最近的聊天记录】
{recent_chat_history}
{expression_habits}
请生成一条简短的消息,用于水群。
【要求】
1. 风格简短随意5-20字
2. 不要提出明确的话题或问题,可以是问候、表达心情或一句随口的话。
3. 符合你的人设和当前聊天风格。
4. **你的心情应该影响消息的内容和语气**。
5. 如果有表达方式参考,在合适时自然使用。
6. 合理参考历史记录。
直接输出消息内容,不要解释:""",
name="proactive_thinking_simple_bubble",
)
# 抛出话题回复 Prompt
throw_topic_reply_prompt_template = Prompt(
"""你的人设是:
{bot_personality}
现在是 {current_time},你决定在与 "{stream_name}" 的对话中主动发起一次互动。
【你当前的心情】
{current_mood}
【聊天环境】
- 整体印象: {stream_impression}
- 聊天风格: {chat_style}
- 常见话题: {topic_keywords}
【最近的聊天记录】
{recent_chat_history}
【你的互动意图】
{topic}
{expression_habits}
【构思指南】
请根据你的互动意图,生成一条有温度的消息。
- 如果意图是**延续约定**(如回应“晚安”),请直接生成对应的问候。
- 如果意图是**表达关心**(如跟进对方提到的事),请生成自然、真诚的关心话语。
- 如果意图是**开启新话题**,请自然地引入话题。
请根据这个意图,生成一条消息,要求:
1. 自然地引入话题或表达关心。
2. 长度适中20-50字
3. 可以结合最近的聊天记录,使对话更连贯。
4. 符合你的人设和当前聊天风格。
5. **你的心情会影响你的表达方式**。
6. 如果有表达方式参考,在合适时自然使用。
直接输出消息内容,不要解释:""",
name="proactive_thinking_throw_topic",
)
class ProactiveThinkingPlanner:
"""主动思考规划器
@@ -167,7 +295,35 @@ class ProactiveThinkingPlanner:
response = None
try:
decision_prompt = self._build_decision_prompt(context)
# 构建上次决策信息
last_decision_text = ""
if context.get("last_decision"):
last_dec = context["last_decision"]
last_action = last_dec.get("action", "未知")
last_reasoning = last_dec.get("reasoning", "")
last_topic = last_dec.get("topic")
last_time = last_dec.get("timestamp", "未知")
last_decision_text = f"""
【上次主动思考的决策】
- 时间: {last_time}
- 决策: {last_action}
- 理由: {last_reasoning}"""
if last_topic:
last_decision_text += f"\n- 话题: {last_topic}"
decision_prompt = decision_prompt_template.format(
bot_personality=context["bot_personality"],
current_time=context["current_time"],
stream_name=context["stream_name"],
current_mood=context.get("current_mood", "感觉很平静"),
stream_impression=context["stream_impression"],
chat_style=context["chat_style"],
topic_keywords=context["topic_keywords"] or "暂无",
interest_score=context["interest_score"],
last_decision_text=last_decision_text,
recent_chat_history=context["recent_chat_history"],
)
if global_config.debug.show_prompt:
logger.info(f"决策提示词:\n{decision_prompt}")
@@ -195,76 +351,6 @@ class ProactiveThinkingPlanner:
logger.error(f"决策过程失败: {e}", exc_info=True)
return None
def _build_decision_prompt(self, context: dict[str, Any]) -> str:
"""构建决策提示词"""
# 构建上次决策信息
last_decision_text = ""
if context.get("last_decision"):
last_dec = context["last_decision"]
last_action = last_dec.get("action", "未知")
last_reasoning = last_dec.get("reasoning", "")
last_topic = last_dec.get("topic")
last_time = last_dec.get("timestamp", "未知")
last_decision_text = f"""
【上次主动思考的决策】
- 时间: {last_time}
- 决策: {last_action}
- 理由: {last_reasoning}"""
if last_topic:
last_decision_text += f"\n- 话题: {last_topic}"
return f"""你的人设是:
{context['bot_personality']}
现在是 {context['current_time']},你正在考虑是否要在与 "{context['stream_name']}" 的对话中主动说些什么。
【你当前的心情】
{context.get("current_mood", "感觉很平静")}
【聊天环境信息】
- 整体印象: {context["stream_impression"]}
- 聊天风格: {context["chat_style"]}
- 常见话题: {context["topic_keywords"] or "暂无"}
- 你的兴趣程度: {context["interest_score"]:.2f}/1.0
{last_decision_text}
【最近的聊天记录】
{context["recent_chat_history"]}
请根据以上信息,决定你现在应该做什么:
**选项1什么都不做 (do_nothing)**
- 适用场景:气氛不适合说话、最近对话很活跃、没什么特别想说的、或者此时说话会显得突兀。
- 心情影响:如果心情不好(如生气、难过),可能更倾向于保持沉默。
**选项2简单冒个泡 (simple_bubble)**
- 适用场景:对话有些冷清,你想缓和气氛或开启新的互动。
- 方式:说一句轻松随意的话,旨在建立或维持连接。
- 心情影响:心情会影响你冒泡的方式和内容。
**选项3发起一次有目的的互动 (throw_topic)**
- 适用场景:你想延续对话、表达关心、或深入讨论某个具体话题。
- **【互动类型1延续约定或提醒】(最高优先级)**:检查最近的聊天记录,是否存在可以延续的互动。例如,如果昨晚的最后一条消息是“晚安”,现在是早上,一个“早安”的回应是绝佳的选择。如果之前提到过某个约定(如“待会聊”),现在可以主动跟进。
- **【互动类型2展现真诚的关心】(次高优先级)**:如果不存在可延续的约定,请仔细阅读聊天记录,寻找对方提及的个人状况(如天气、出行、身体、情绪、工作学习等),并主动表达关心。
- **【互动类型3开启新话题】**:当以上两点都不适用时,可以考虑开启一个你感兴趣的新话题。
- 心情影响:心情会影响你想发起互动的方式和内容。
请以JSON格式回复你的决策
{{
"action": "do_nothing" | "simple_bubble" | "throw_topic",
"reasoning": "你的决策理由(请结合你的心情、聊天环境和对话历史进行分析)",
"topic": "(仅当action=throw_topic时填写)你的互动意图(如:回应晚安并说早安、关心对方的考试情况、讨论新游戏)"
}}
注意:
1. 兴趣度较低(<0.4)时或者最近聊天很活跃不到1小时倾向于 `do_nothing` 或 `simple_bubble`。
2. 你的心情会影响你的行动倾向和表达方式。
3. 参考上次决策,避免重复,并可根据上次的互动效果调整策略。
4. 只有在真的有感而发时才选择 `throw_topic`。
5. 保持你的人设,确保行为一致性。
"""
async def generate_reply(
self, context: dict[str, Any], action: Literal["simple_bubble", "throw_topic"], topic: str | None = None
) -> str | None:
@@ -283,7 +369,34 @@ class ProactiveThinkingPlanner:
return None
try:
reply_prompt = await self._build_reply_prompt(context, action, topic)
# 获取表达方式参考
expression_habits = await self._get_expression_habits(
stream_id=context.get("stream_id", ""), chat_history=context.get("recent_chat_history", "")
)
if action == "simple_bubble":
reply_prompt = simple_bubble_reply_prompt_template.format(
bot_personality=context["bot_personality"],
current_mood=context.get("current_mood", "感觉很平静"),
stream_impression=context["stream_impression"],
chat_style=context["chat_style"],
recent_chat_history=context["recent_chat_history"],
expression_habits=expression_habits,
)
else: # throw_topic
reply_prompt = throw_topic_reply_prompt_template.format(
bot_personality=context["bot_personality"],
current_time=context["current_time"],
stream_name=context["stream_name"],
current_mood=context.get("current_mood", "感觉很平静"),
stream_impression=context["stream_impression"],
chat_style=context["chat_style"],
topic_keywords=context["topic_keywords"] or "暂无",
recent_chat_history=context["recent_chat_history"],
topic=topic,
expression_habits=expression_habits,
)
if global_config.debug.show_prompt:
logger.info(f"回复提示词:\n{reply_prompt}")
@@ -350,77 +463,6 @@ class ProactiveThinkingPlanner:
logger.warning(f"获取表达方式失败: {e}")
return ""
async def _build_reply_prompt(
self, context: dict[str, Any], action: Literal["simple_bubble", "throw_topic"], topic: str | None
) -> str:
"""构建回复提示词"""
# 获取表达方式参考
expression_habits = await self._get_expression_habits(
stream_id=context.get("stream_id", ""), chat_history=context.get("recent_chat_history", "")
)
if action == "simple_bubble":
return f"""你的人设是:
{context['bot_personality']}
距离上次对话已经有一段时间了,你决定主动说些什么,轻松地开启新的互动。
【你当前的心情】
{context.get("current_mood", "感觉很平静")}
【聊天环境】
- 整体印象: {context["stream_impression"]}
- 聊天风格: {context["chat_style"]}
【最近的聊天记录】
{context["recent_chat_history"]}
{expression_habits}
请生成一条简短的消息,用于水群。
【要求】
1. 风格简短随意5-20字
2. 不要提出明确的话题或问题,可以是问候、表达心情或一句随口的话。
3. 符合你的人设和当前聊天风格。
4. **你的心情应该影响消息的内容和语气**。
5. 如果有表达方式参考,在合适时自然使用。
6. 合理参考历史记录。
直接输出消息内容,不要解释:"""
else: # throw_topic
return f"""你的人设是:
{context['bot_personality']}
现在是 {context['current_time']},你决定在与 "{context['stream_name']}" 的对话中主动发起一次互动。
【你当前的心情】
{context.get("current_mood", "感觉很平静")}
【聊天环境】
- 整体印象: {context["stream_impression"]}
- 聊天风格: {context["chat_style"]}
- 常见话题: {context["topic_keywords"] or "暂无"}
【最近的聊天记录】
{context["recent_chat_history"]}
【你的互动意图】
{topic}
{expression_habits}
【构思指南】
请根据你的互动意图,生成一条有温度的消息。
- 如果意图是**延续约定**(如回应“晚安”),请直接生成对应的问候。
- 如果意图是**表达关心**(如跟进对方提到的事),请生成自然、真诚的关心话语。
- 如果意图是**开启新话题**,请自然地引入话题。
请根据这个意图,生成一条消息,要求:
1. 自然地引入话题或表达关心。
2. 长度适中20-50字
3. 可以结合最近的聊天记录,使对话更连贯。
4. 符合你的人设和当前聊天风格。
5. **你的心情会影响你的表达方式**。
6. 如果有表达方式参考,在合适时自然使用。
直接输出消息内容,不要解释:"""
def _clean_json_response(self, response: str) -> str:
"""清理LLM响应中的JSON格式标记"""
import re