feat(chat): 实现多重回复功能,允许单轮处理多条消息
本次更新引入了核心的多重回复功能,使得 Bot 能够在单次规划中响应多条不同的未读消息。这显著提升了其在活跃群聊中的交互效率和上下文处理能力。 主要变更包括: - **Planner Prompts 更新**: - 增加了明确的指令和示例,引导 LLM 在检测到多个需要回应的消息时,生成一个包含多个 `reply` 动作的 `actions` 列表。 - **PlanExecutor 逻辑增强**: - `_execute_reply_actions` 方法被重构,以串行方式执行计划中的所有回复动作。 - 引入了新的控制逻辑,确保只有在执行完最后一个 `reply` 动作后,才会清除未读消息队列。 - **ActionManager 功能扩展**: - `execute_action` 方法新增 `clear_unread_messages` 参数(默认为 `True`),允许调用方控制是否在动作执行后清空未读消息。 - `PlanExecutor` 在调用非 `reply` 动作或非最后一个 `reply` 动作时,会显式将此参数设为 `False`,防止过早清除消息。
This commit is contained in:
@@ -145,6 +145,7 @@ class ChatterActionManager:
|
|||||||
action_data: Optional[dict] = None,
|
action_data: Optional[dict] = None,
|
||||||
thinking_id: Optional[str] = None,
|
thinking_id: Optional[str] = None,
|
||||||
log_prefix: str = "",
|
log_prefix: str = "",
|
||||||
|
clear_unread_messages: bool = True,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
执行单个动作的通用函数
|
执行单个动作的通用函数
|
||||||
@@ -217,6 +218,7 @@ class ChatterActionManager:
|
|||||||
if success:
|
if success:
|
||||||
await self._record_action_to_message(chat_stream, action_name, target_message, action_data)
|
await self._record_action_to_message(chat_stream, action_name, target_message, action_data)
|
||||||
# 自动清空所有未读消息
|
# 自动清空所有未读消息
|
||||||
|
if clear_unread_messages:
|
||||||
await self._clear_all_unread_messages(chat_stream.stream_id, action_name)
|
await self._clear_all_unread_messages(chat_stream.stream_id, action_name)
|
||||||
# 重置打断计数
|
# 重置打断计数
|
||||||
await self._reset_interruption_count_after_action(chat_stream.stream_id)
|
await self._reset_interruption_count_after_action(chat_stream.stream_id)
|
||||||
@@ -262,7 +264,7 @@ class ChatterActionManager:
|
|||||||
# 记录回复动作到目标消息
|
# 记录回复动作到目标消息
|
||||||
await self._record_action_to_message(chat_stream, "reply", target_message, action_data)
|
await self._record_action_to_message(chat_stream, "reply", target_message, action_data)
|
||||||
|
|
||||||
# 自动清空所有未读消息
|
if clear_unread_messages:
|
||||||
await self._clear_all_unread_messages(chat_stream.stream_id, "reply")
|
await self._clear_all_unread_messages(chat_stream.stream_id, "reply")
|
||||||
|
|
||||||
# 回复成功,重置打断计数
|
# 回复成功,重置打断计数
|
||||||
|
|||||||
@@ -111,16 +111,26 @@ class ChatterPlanExecutor:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async def _execute_reply_actions(self, reply_actions: List[ActionPlannerInfo], plan: Plan) -> Dict[str, any]:
|
async def _execute_reply_actions(self, reply_actions: List[ActionPlannerInfo], plan: Plan) -> Dict[str, any]:
|
||||||
"""执行回复动作"""
|
"""串行执行所有回复动作"""
|
||||||
results = []
|
results = []
|
||||||
|
total_actions = len(reply_actions)
|
||||||
|
if total_actions > 1:
|
||||||
|
logger.info(f"[多重回复] 开始执行 {total_actions} 个回复任务。")
|
||||||
|
|
||||||
for action_info in reply_actions:
|
for i, action_info in enumerate(reply_actions):
|
||||||
result = await self._execute_single_reply_action(action_info, plan)
|
is_last_action = i == total_actions - 1
|
||||||
|
if total_actions > 1:
|
||||||
|
logger.info(f"[多重回复] 正在执行第 {i+1}/{total_actions} 个回复...")
|
||||||
|
|
||||||
|
# 传递 clear_unread 参数
|
||||||
|
result = await self._execute_single_reply_action(action_info, plan, clear_unread=is_last_action)
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
|
if total_actions > 1:
|
||||||
|
logger.info(f"[多重回复] 所有回复任务执行完毕。")
|
||||||
return {"results": results}
|
return {"results": results}
|
||||||
|
|
||||||
async def _execute_single_reply_action(self, action_info: ActionPlannerInfo, plan: Plan) -> Dict[str, any]:
|
async def _execute_single_reply_action(self, action_info: ActionPlannerInfo, plan: Plan, clear_unread: bool = True) -> Dict[str, any]:
|
||||||
"""执行单个回复动作"""
|
"""执行单个回复动作"""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
success = False
|
success = False
|
||||||
@@ -152,17 +162,29 @@ class ChatterPlanExecutor:
|
|||||||
"target_message": action_info.action_message,
|
"target_message": action_info.action_message,
|
||||||
"reasoning": action_info.reasoning,
|
"reasoning": action_info.reasoning,
|
||||||
"action_data": action_info.action_data or {},
|
"action_data": action_info.action_data or {},
|
||||||
|
"clear_unread_messages": clear_unread,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(f"📬 [PlanExecutor] 准备调用 ActionManager,target_message: {action_info.action_message}")
|
logger.debug(f"📬 [PlanExecutor] 准备调用 ActionManager,target_message: {action_info.action_message}")
|
||||||
|
|
||||||
# 通过动作管理器执行回复
|
# 通过动作管理器执行回复
|
||||||
reply_content = await self.action_manager.execute_action(
|
execution_result = await self.action_manager.execute_action(
|
||||||
action_name=action_info.action_type, **action_params
|
action_name=action_info.action_type, **action_params
|
||||||
)
|
)
|
||||||
|
|
||||||
success = True
|
# 从返回结果中提取真正的回复文本
|
||||||
|
if isinstance(execution_result, dict):
|
||||||
|
reply_content = execution_result.get("reply_text", "")
|
||||||
|
success = execution_result.get("success", False)
|
||||||
|
else:
|
||||||
|
# 兼容旧的返回值(虽然可能性不大)
|
||||||
|
reply_content = str(execution_result) if execution_result else ""
|
||||||
|
success = bool(reply_content)
|
||||||
|
|
||||||
|
if success:
|
||||||
logger.info(f"回复动作 '{action_info.action_type}' 执行成功。")
|
logger.info(f"回复动作 '{action_info.action_type}' 执行成功。")
|
||||||
|
else:
|
||||||
|
raise Exception(execution_result.get("error", "未知错误"))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_message = str(e)
|
error_message = str(e)
|
||||||
@@ -181,7 +203,7 @@ class ChatterPlanExecutor:
|
|||||||
"error_message": error_message,
|
"error_message": error_message,
|
||||||
"execution_time": execution_time,
|
"execution_time": execution_time,
|
||||||
"reasoning": action_info.reasoning,
|
"reasoning": action_info.reasoning,
|
||||||
"reply_content": reply_content[:200] + "..." if len(reply_content) > 200 else reply_content,
|
"reply_content": reply_content[:200] + "..." if reply_content and len(reply_content) > 200 else reply_content,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _execute_other_actions(self, other_actions: List[ActionPlannerInfo], plan: Plan) -> Dict[str, any]:
|
async def _execute_other_actions(self, other_actions: List[ActionPlannerInfo], plan: Plan) -> Dict[str, any]:
|
||||||
@@ -251,6 +273,7 @@ class ChatterPlanExecutor:
|
|||||||
"target_message": action_info.action_message,
|
"target_message": action_info.action_message,
|
||||||
"reasoning": action_info.reasoning,
|
"reasoning": action_info.reasoning,
|
||||||
"action_data": action_data,
|
"action_data": action_data,
|
||||||
|
"clear_unread_messages": False, # 其他动作不应清除未读消息
|
||||||
}
|
}
|
||||||
|
|
||||||
# 通过动作管理器执行动作
|
# 通过动作管理器执行动作
|
||||||
|
|||||||
@@ -44,10 +44,11 @@ def init_prompts():
|
|||||||
1. **重要:已读历史消息仅作为当前聊天情景的参考,帮助你理解对话上下文。**
|
1. **重要:已读历史消息仅作为当前聊天情景的参考,帮助你理解对话上下文。**
|
||||||
2. **重要:所有动作的执行对象只能是未读历史消息中的消息,不能对已读消息执行动作。**
|
2. **重要:所有动作的执行对象只能是未读历史消息中的消息,不能对已读消息执行动作。**
|
||||||
3. 在未读历史消息中,优先对兴趣值高的消息做出动作(兴趣值标注在消息末尾)。
|
3. 在未读历史消息中,优先对兴趣值高的消息做出动作(兴趣值标注在消息末尾)。
|
||||||
4. 首先,决定是否要对未读消息进行 `reply`(如果有)。
|
4. **核心:如果有多条未读消息都需要回应(例如多人@你),你应该并行处理,在`actions`列表中生成多个`reply`动作。**
|
||||||
5. 然后,评估当前的对话气氛和用户情绪,判断是否需要一个**辅助动作**来让你的回应更生动、更符合你的性格。
|
5. 首先,决定是否要对未读消息进行 `reply`(如果有)。
|
||||||
6. 如果需要,选择一个最合适的辅助动作与 `reply`(如果有) 组合。
|
6. 然后,评估当前的对话气氛和用户情绪,判断是否需要一个**辅助动作**来让你的回应更生动、更符合你的性格。
|
||||||
7. 如果用户明确要求了某个动作,请务必优先满足。
|
7. 如果需要,选择一个最合适的辅助动作与 `reply`(如果有) 组合。
|
||||||
|
8. 如果用户明确要求了某个动作,请务必优先满足。
|
||||||
|
|
||||||
**重要提醒:**
|
**重要提醒:**
|
||||||
- **回复消息时必须遵循对话的流程,不要重复已经说过的话。**
|
- **回复消息时必须遵循对话的流程,不要重复已经说过的话。**
|
||||||
@@ -70,6 +71,7 @@ def init_prompts():
|
|||||||
## 可用动作列表
|
## 可用动作列表
|
||||||
{action_options_text}
|
{action_options_text}
|
||||||
|
|
||||||
|
### 单动作示例:
|
||||||
```json
|
```json
|
||||||
{{
|
{{
|
||||||
"thinking": "在这里写下你的思绪流...",
|
"thinking": "在这里写下你的思绪流...",
|
||||||
@@ -86,6 +88,40 @@ def init_prompts():
|
|||||||
}}
|
}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### **多重回复示例 (核心功能)**
|
||||||
|
当有多人与你互动时,你需要同时回应他们,甚至可以同时处理三个!
|
||||||
|
```json
|
||||||
|
{{
|
||||||
|
"thinking": "哇,群里好热闹呀!张三、李四、王五都在@我!让我看看...张三在问我昨天推荐的电影好不好看,这个得好好分享一下观后感。李四在说他家的猫咪学会了新技能,好可爱,得夸夸他!王五好像遇到点麻烦,在问我一个技术问题,这个得优先、详细地解答一下!得一个个来!",
|
||||||
|
"actions": [
|
||||||
|
{{
|
||||||
|
"action_type": "reply",
|
||||||
|
"reasoning": "回应张三关于电影的提问,并分享我的看法。",
|
||||||
|
"action_data": {{
|
||||||
|
"target_message_id": "m124",
|
||||||
|
"content": "张三!你问的那部电影我昨天也看啦,真的超赞!特别是最后那个反转,简直让人意想不到!"
|
||||||
|
}}
|
||||||
|
}},
|
||||||
|
{{
|
||||||
|
"action_type": "reply",
|
||||||
|
"reasoning": "回应李四分享的趣事,表达赞美和羡慕。",
|
||||||
|
"action_data": {{
|
||||||
|
"target_message_id": "m125",
|
||||||
|
"content": "哇,李四你家猫咪也太聪明了吧!居然会握手了!好羡慕呀!"
|
||||||
|
}}
|
||||||
|
}},
|
||||||
|
{{
|
||||||
|
"action_type": "reply",
|
||||||
|
"reasoning": "优先回应王五的技术求助,并提供详细的解答。",
|
||||||
|
"action_data": {{
|
||||||
|
"target_message_id": "m126",
|
||||||
|
"content": "王五别急,你说的那个问题我之前也遇到过。你试试看是不是配置文件里的`enable_magic`选项没有设置成`true`?如果还不行你再把错误截图发我看看。"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
```
|
||||||
|
|
||||||
**强制规则**:
|
**强制规则**:
|
||||||
- 对于每一个需要目标消息的动作(如`reply`, `poke_user`, `set_emoji_like`),你 **必须** 在`action_data`中提供准确的`target_message_id`,这个ID来源于`## 未读历史消息`中消息前的`<m...>`标签。
|
- 对于每一个需要目标消息的动作(如`reply`, `poke_user`, `set_emoji_like`),你 **必须** 在`action_data`中提供准确的`target_message_id`,这个ID来源于`## 未读历史消息`中消息前的`<m...>`标签。
|
||||||
- 当你选择的动作需要参数时(例如 `set_emoji_like` 需要 `emoji` 参数),你 **必须** 在 `action_data` 中提供所有必需的参数及其对应的值。
|
- 当你选择的动作需要参数时(例如 `set_emoji_like` 需要 `emoji` 参数),你 **必须** 在 `action_data` 中提供所有必需的参数及其对应的值。
|
||||||
|
|||||||
Reference in New Issue
Block a user