feat(chatter): 添加should_quote_reply参数以控制引用回复行为

This commit is contained in:
Windpicker-owo
2025-10-31 22:36:53 +08:00
parent c83d1ac1d8
commit d53ee349f4
6 changed files with 77 additions and 15 deletions

View File

@@ -277,6 +277,11 @@ class ChatterActionManager:
logger.debug(f"{log_prefix} 并行执行:回复生成任务已被取消")
return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
# 从action_data中提取should_quote_reply参数
should_quote_reply = None
if action_data and isinstance(action_data, dict):
should_quote_reply = action_data.get("should_quote_reply", None)
# 发送并存储回复
loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply(
chat_stream,
@@ -286,6 +291,7 @@ class ChatterActionManager:
{}, # cycle_timers
thinking_id,
[], # actions
should_quote_reply, # 传递should_quote_reply参数
)
# 记录回复动作到目标消息
@@ -474,6 +480,7 @@ class ChatterActionManager:
cycle_timers: dict[str, float],
thinking_id,
actions,
should_quote_reply: bool | None = None,
) -> tuple[dict[str, Any], str, dict[str, float]]:
"""
发送并存储回复信息
@@ -486,13 +493,16 @@ class ChatterActionManager:
cycle_timers: 循环计时器
thinking_id: 思考ID
actions: 动作列表
should_quote_reply: 是否应该引用回复原消息None表示自动决定
Returns:
Tuple[Dict[str, Any], str, Dict[str, float]]: 循环信息, 回复文本, 循环计时器
"""
# 发送回复
with Timer("回复发送", cycle_timers):
reply_text = await self.send_response(chat_stream, response_set, loop_start_time, action_message)
reply_text = await self.send_response(
chat_stream, response_set, loop_start_time, action_message, should_quote_reply
)
# 存储reply action信息
person_info_manager = get_person_info_manager()
@@ -551,16 +561,18 @@ class ChatterActionManager:
return loop_info, reply_text, cycle_timers
async def send_response(self, chat_stream, reply_set, thinking_start_time, message_data) -> str:
async def send_response(
self, chat_stream, reply_set, thinking_start_time, message_data, should_quote_reply: bool | None = None
) -> str:
"""
发送回复内容的具体实现
Args:
chat_stream: ChatStream实例
reply_set: 回复内容集合,包含多个回复段
reply_to: 回复目标
thinking_start_time: 思考开始时间
message_data: 消息数据
should_quote_reply: 是否应该引用回复原消息None表示自动决定
Returns:
str: 完整的回复文本
@@ -614,12 +626,24 @@ class ChatterActionManager:
# 发送第一段回复
if not first_replied:
# 私聊场景不使用引用回复(因为只有两个人对话,引用是多余的)
# 群聊场景使用引用回复(帮助定位回复的目标消息)
# 决定是否引用回复
is_private_chat = not bool(chat_stream.group_info)
set_reply_flag = bool(message_data) and not is_private_chat
# 如果明确指定了should_quote_reply则使用指定值
if should_quote_reply is not None:
set_reply_flag = should_quote_reply and bool(message_data)
logger.debug(
f"📤 [ActionManager] 使用planner指定的引用设置: should_quote_reply={should_quote_reply}"
)
else:
# 否则使用默认逻辑:默认不引用,让对话更流畅自然
set_reply_flag = False
logger.debug(
f"📤 [ActionManager] 使用默认引用逻辑: 默认不引用(is_private={is_private_chat})"
)
logger.debug(
f"📤 [ActionManager] 准备发送第一段回复。message_data: {message_data}, is_private: {is_private_chat}, set_reply: {set_reply_flag}"
f"📤 [ActionManager] 准备发送第一段回复。message_data: {message_data}, set_reply: {set_reply_flag}"
)
await send_api.text_to_stream(
text=data,

View File

@@ -27,6 +27,7 @@ class ActionPlannerInfo(BaseDataModel):
action_data: dict | None = None
action_message: Optional["DatabaseMessages"] = None
available_actions: dict[str, "ActionInfo"] | None = None
should_quote_reply: bool | None = None # 是否应该引用回复原消息None表示由系统自动决定
@dataclass

View File

@@ -197,15 +197,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

@@ -607,6 +607,14 @@ class ChatterPlanFilter:
except Exception:
logger.warning("无法将目标消息转换为DatabaseMessages对象")
# 从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,
@@ -614,6 +622,7 @@ class ChatterPlanFilter:
action_data=action_data,
action_message=action_message_obj,
available_actions=plan.available_actions,
should_quote_reply=should_quote_reply, # 传递should_quote_reply参数
)
)
except Exception as e:

View File

@@ -319,6 +319,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 中补全。