diff --git a/src/chat/chat_loop/cycle_processor.py b/src/chat/chat_loop/cycle_processor.py index 739b3c183..fe993f484 100644 --- a/src/chat/chat_loop/cycle_processor.py +++ b/src/chat/chat_loop/cycle_processor.py @@ -205,6 +205,13 @@ class CycleProcessor: raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成") with Timer("规划器", cycle_timers): actions, _ = await self.action_planner.plan(mode=mode) + + # 在这里添加日志,清晰地显示最终选择的动作 + if actions: + chosen_actions = [a.get("action_type", "unknown") for a in actions] + logger.info(f"{self.log_prefix} LLM最终选择的动作: {chosen_actions}") + else: + logger.info(f"{self.log_prefix} LLM最终没有选择任何动作") async def execute_action(action_info): """执行单个动作的通用函数""" diff --git a/src/chat/planner_actions/planner_prompts.py b/src/chat/planner_actions/planner_prompts.py index 60b20c15b..d527655c8 100644 --- a/src/chat/planner_actions/planner_prompts.py +++ b/src/chat/planner_actions/planner_prompts.py @@ -35,10 +35,11 @@ def init_prompts(): 2. **辅助动作 (可选)**: 这是为了增强表达效果的附加动作,例如 `emoji`(发送表情包)或 `poke_user`(戳一戳)。 **决策流程:** -1. 首先,决定是否要进行 `reply`。 -2. 然后,评估当前的对话气氛和用户情绪,判断是否需要一个**辅助动作**来让你的回应更生动、更符合你的性格。 -3. 如果需要,选择一个最合适的辅助动作与 `reply` 组合。 -4. 如果用户明确要求了某个动作,请务必优先满足。 +1. **最高优先级检查**: 首先,检查是否有由 **关键词** 或 **LLM判断** 激活的特定动作(除了通用的 `reply`, `emoji` 等)。这些动作代表了用户的明确意图。 +2. **执行明确意图**: 如果存在这类特定动作,你 **必须** 优先选择它作为主要响应。这比常规的文本回复 (`reply`) 更重要。 +3. **常规回复**: 如果没有被特定意图激活的动作,再决定是否要进行 `reply`。 +4. **辅助动作**: 在确定了主要动作后(无论是特定动作还是 `reply`),再评估是否需要 `emoji` 或 `poke_user` 等辅助动作来增强表达效果。 +5. **互斥原则**: 当你选择了一个由明确意图激活的特定动作(如 `set_reminder`)时,你 **绝不能** 再选择 `reply` 动作,因为特定动作的执行结果(例如,设置提醒后的确认消息)本身就是一种回复。这是必须遵守的规则。 **重要概念:将“理由”作为“内心思考”的体现** `reason` 字段是本次决策的核心。它并非一个简单的“理由”,而是 **一个模拟人类在回应前,头脑中自然浮现的、未经修饰的思绪流**。你需要完全代入 {identity_block} 的角色,将那一刻的想法自然地记录下来。 @@ -100,6 +101,18 @@ def init_prompts(): }} ] +**单动作示例 (特定动作):** +[ + {{ + "action": "set_reminder", + "target_message_id": "m456", + "reason": "用户说‘提醒维尔薇下午三点去工坊’,这是一个非常明确的指令。根据决策流程,我必须优先执行这个特定动作,而不是进行常规回复。", + "user_name": "维尔薇", + "remind_time": "下午三点", + "event_details": "去工坊" + }} +] + **重要规则:** **重要规则:** 当 `reply` 和 `emoji` 动作同时被选择时,`emoji` 动作的 `reason` 字段也应该体现出你的思考过程,并与 `reply` 的思考保持连贯。 diff --git a/src/plugins/built_in/reminder_plugin/plugin.py b/src/plugins/built_in/reminder_plugin/plugin.py index 5ed25c6ff..43136cea5 100644 --- a/src/plugins/built_in/reminder_plugin/plugin.py +++ b/src/plugins/built_in/reminder_plugin/plugin.py @@ -1,6 +1,6 @@ import asyncio from datetime import datetime -from typing import List, Tuple, Type +from typing import List, Tuple, Type, Optional from dateutil.parser import parse as parse_datetime from src.common.logger import get_logger @@ -22,10 +22,11 @@ logger = get_logger(__name__) # ============================ AsyncTask ============================ class ReminderTask(AsyncTask): - def __init__(self, delay: float, stream_id: str, is_group: bool, target_user_id: str, target_user_name: str, event_details: str, creator_name: str): + def __init__(self, delay: float, stream_id: str, group_id: Optional[str], is_group: bool, target_user_id: str, target_user_name: str, event_details: str, creator_name: str): super().__init__(task_name=f"ReminderTask_{target_user_id}_{datetime.now().timestamp()}") self.delay = delay self.stream_id = stream_id + self.group_id = group_id self.is_group = is_group self.target_user_id = target_user_id self.target_user_name = target_user_name @@ -44,14 +45,13 @@ class ReminderTask(AsyncTask): if self.is_group: # 在群聊中,构造 @ 消息段并发送 - group_id = self.stream_id.split('_')[-1] if '_' in self.stream_id else self.stream_id message_payload = [ {"type": "at", "data": {"qq": self.target_user_id}}, {"type": "text", "data": {"text": f" {reminder_text}"}} ] await send_api.adapter_command_to_stream( action="send_group_msg", - params={"group_id": group_id, "message": message_payload}, + params={"group_id": self.group_id, "message": message_payload}, stream_id=self.stream_id ) else: @@ -83,35 +83,8 @@ class RemindAction(BaseAction): ) # === LLM 判断与参数提取 === - llm_judge_prompt = """ - 你是一个严格的提醒意图分类器。你的任务是判断用户是否明确意图设置一个未来的提醒。这是一个最高优先级的任务。 - - **规则:** - 1. 必须包含一个明确的、指向未来的时间点或时间段(例如:“十分钟后”、“明天下午3点”、“周五”、“待会儿”、“一分钟后”)。 - 2. 必须包含一个需要被提醒的具体事件或动作(例如:“开会”、“喝水”、“睡觉”、“去吃饭”)。 - 3. 如果文本同时满足规则1和2,你必须,且只能回答“是”。 - 4. 任何不满足上述两个核心规则的文本,都回答“否”。 - - **正面示例(必须回答“是”):** - - "半小时后提醒我开会" - - "两分钟后叫我喝水" - - "爱莉,提醒一闪一分钟后去睡觉" - - "别忘了周五把报告交了" - - "待会儿记得和我说一声" - - **负面示例(必须回答“否”):** - - "现在几点了?" (只是询问时间) - - "我明天下午有空" (陈述事实,没有要求提醒) - - "提醒呢?" (询问提醒状态,而不是设置新提醒) - - "我记得了" (表示自己记住了,而不是让bot记住) - - 请严格按照规则进行分类,只回答"是"或"否"。 - """ - action_parameters = { - "user_name": "需要被提醒的人的称呼或名字,如果没有明确指定给某人,则默认为'自己'", - "remind_time": "描述提醒时间的自然语言字符串,例如'十分钟后'或'明天下午3点'", - "event_details": "需要提醒的具体事件内容" - } + llm_judge_prompt = "" + action_parameters = {} action_require = [ "当用户请求在未来的某个时间点提醒他/她或别人某件事时使用", "适用于包含明确时间信息和事件描述的对话", @@ -120,9 +93,52 @@ class RemindAction(BaseAction): async def execute(self) -> Tuple[bool, str]: """执行设置提醒的动作""" - user_name = self.action_data.get("user_name") - remind_time_str = self.action_data.get("remind_time") - event_details = self.action_data.get("event_details") + try: + # 获取所有可用的模型配置 + available_models = llm_api.get_available_models() + if "planner" not in available_models: + raise ValueError("未找到 'planner' 决策模型配置,无法解析时间") + model_to_use = available_models["planner"] + + prompt = f""" + 从以下用户输入中提取提醒事件的关键信息。 + 用户输入: "{self.chat_stream.context.message.processed_plain_text}" + + 请以JSON格式返回提取的信息,包含以下字段: + - "user_name": 需要被提醒的人的姓名。如果未指定,则默认为"自己"。 + - "remind_time": 描述提醒时间的自然语言字符串。 + - "event_details": 需要提醒的具体事件内容。 + + 如果无法提取完整信息,请返回一个包含空字符串的JSON对象,例如:{{"user_name": "", "remind_time": "", "event_details": ""}} + """ + + success, response, _, _ = await llm_api.generate_with_model( + prompt, + model_config=model_to_use, + request_type="plugin.reminder.parameter_extractor" + ) + + if not success or not response: + raise ValueError(f"LLM未能返回有效的参数: {response}") + + import json + import re + try: + # 提取JSON部分 + json_match = re.search(r"\{.*\}", response, re.DOTALL) + if not json_match: + raise ValueError("LLM返回的内容中不包含JSON") + action_data = json.loads(json_match.group(0)) + except json.JSONDecodeError: + logger.error(f"[ReminderPlugin] LLM返回的不是有效的JSON: {response}") + return False, "LLM返回的不是有效的JSON" + user_name = action_data.get("user_name") + remind_time_str = action_data.get("remind_time") + event_details = action_data.get("event_details") + + except Exception as e: + logger.error(f"[ReminderPlugin] 解析参数时出错: {e}", exc_info=True) + return False, "解析参数时出错" if not all([user_name, remind_time_str, event_details]): missing_params = [p for p, v in {"user_name": user_name, "remind_time": remind_time_str, "event_details": event_details}.items() if not v] @@ -208,7 +224,8 @@ class RemindAction(BaseAction): reminder_task = ReminderTask( delay=delay_seconds, - stream_id=self.chat_id, + stream_id=self.chat_stream.stream_id, + group_id=self.chat_stream.group_info.group_id if self.is_group and self.chat_stream.group_info else None, is_group=self.is_group, target_user_id=str(user_id_to_remind), target_user_name=str(user_name_to_remind),