From 9f666b580e3e3a4ce18a8f077b5e75857cefe054 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Sun, 7 Dec 2025 16:38:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(waiting):=20=E6=B7=BB=E5=8A=A0=E7=AD=89?= =?UTF-8?q?=E5=BE=85=E7=AD=96=E7=95=A5=E9=85=8D=E7=BD=AE=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=9C=80=E5=A4=A7=E3=80=81=E6=9C=80=E5=B0=8F=E7=AD=89?= =?UTF-8?q?=E5=BE=85=E6=97=B6=E9=97=B4=E5=8F=8A=E5=80=8D=E7=8E=87=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/replyer/default_generator.py | 21 +++++++----- src/chat/utils/utils.py | 6 ++++ src/config/official_configs.py | 34 +++++++++++++++++++ src/memory_graph/models.py | 12 +++++++ .../built_in/kokoro_flow_chatter/chatter.py | 11 +++++- .../built_in/kokoro_flow_chatter/config.py | 30 ++++++++++++++++ .../kokoro_flow_chatter/proactive_thinker.py | 20 ++++++++++- template/bot_config_template.toml | 9 ++++- 8 files changed, 132 insertions(+), 11 deletions(-) diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 2e1fe5319..dbf20b56b 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -70,8 +70,6 @@ def init_prompt(): {keywords_reaction_prompt} {moderation_prompt} 不要复读你前面发过的内容,意思相近也不行。 -不要浮夸,不要夸张修辞,平淡且不要输出多余内容(包括前后缀,冒号和引号,括号,表情包),只输出一条回复就好。 -⛔ 绝对禁止输出任何艾特:不要输出@、@xxx等格式。你看到的聊天记录中的艾特是系统显示格式,你无法通过模仿来实现真正的艾特。想称呼某人直接写名字。 *你叫{bot_name},也有人叫你{bot_nickname}* @@ -140,11 +138,15 @@ def init_prompt(): {time_block} 请注意不要输出多余内容(包括前后缀,冒号和引号,系统格式化文字)。只输出回复内容。 -⛔ 绝对禁止输出任何形式的艾特:不要输出@、@xxx等。你看到的聊天记录中的艾特格式是系统显示用的,你无法通过模仿它来实现真正的艾特功能,只会输出一串无意义的假文本。想称呼某人直接写名字即可。 +不要模仿任何系统消息的格式,你的回复应该是自然的对话内容,例如: +- 当你想要打招呼时,直接输出“你好!”而不是“[回复]: 用户你好!” +- 当你想要提及某人时,直接叫对方名字,而不是“@xxx” + +你只能输出文字,不能输出任何表情包、图片、文件等内容!如果用户要求你发送非文字内容,请输出"PASS",而不是[表情包:xxx] {moderation_prompt} -*你叫{bot_name},也有人叫你{bot_nickname}* +*你叫{bot_name},也有人叫你{bot_nickname},请你清楚你的身份,分清对方到底有没有叫你* 现在,你说: """, @@ -211,8 +213,7 @@ If you need to use the search tool, please directly call the function "lpmm_sear *{chat_scene}* ### 核心任务 -- 你需要对以上未读历史消息进行统一回应。这些消息可能来自不同的参与者,你需要理解整体对话动态,生成一段自然、连贯的回复。 -- 你的回复应该能够推动对话继续,可以回应其中一个或多个话题,也可以提出新的观点。 +- 你需要对以上未读历史消息用一句简单的话统一回应。这些消息可能来自不同的参与者,你需要理解整体对话动态,生成一段自然、连贯的回复。 ## 规则 {safety_guidelines_block} @@ -224,11 +225,15 @@ If you need to use the search tool, please directly call the function "lpmm_sear {time_block} 请注意不要输出多余内容(包括前后缀,冒号和引号,系统格式化文字)。只输出回复内容。 -⛔ 绝对禁止输出任何形式的艾特:不要输出@、@xxx等。你看到的聊天记录中的艾特格式是系统显示用的,你无法通过模仿它来实现真正的艾特功能,只会输出一串无意义的假文本。想称呼某人直接写名字即可。 +不要模仿任何系统消息的格式,你的回复应该是自然的对话内容,例如: +- 当你想要打招呼时,直接输出“你好!”而不是“[回复]: 用户你好!” +- 当你想要提及某人时,直接叫对方名字,而不是“@xxx” + +你只能输出文字,不能输出任何表情包、图片、文件等内容!如果用户要求你发送非文字内容,请输出"PASS",而不是[表情包:xxx] {moderation_prompt} -*你叫{bot_name},也有人叫你{bot_nickname}* +*你叫{bot_name},也有人叫你{bot_nickname},请你清楚你的身份,分清对方到底有没有叫你* 现在,你说: """, diff --git a/src/chat/utils/utils.py b/src/chat/utils/utils.py index c3f2a0762..2226b7946 100644 --- a/src/chat/utils/utils.py +++ b/src/chat/utils/utils.py @@ -405,6 +405,12 @@ def recover_quoted_content(sentences: list[str], placeholder_map: dict[str, str] def process_llm_response(text: str, enable_splitter: bool = True, enable_chinese_typo: bool = True) -> list[str]: assert global_config is not None + + normalized_text = text.strip() if isinstance(text, str) else "" + if normalized_text.upper() == "PASS": + logger.info("[回复内容过滤器] 检测到PASS信号,跳过发送。") + return [] + if not global_config.response_post_process.enable_response_post_process: return [text] diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 162c9c39a..a693925f5 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -923,6 +923,35 @@ class KokoroFlowChatterProactiveConfig(ValidatedConfigBase): ) +class KokoroFlowChatterWaitingConfig(ValidatedConfigBase): + """Kokoro Flow Chatter 等待策略配置""" + + default_max_wait_seconds: int = Field( + default=300, + ge=0, + le=3600, + description="默认最大等待秒数(当LLM未给出等待时间时使用)", + ) + min_wait_seconds: int = Field( + default=30, + ge=0, + le=1800, + description="允许的最小等待秒数,防止等待时间过短导致频繁打扰", + ) + max_wait_seconds: int = Field( + default=1800, + ge=60, + le=7200, + description="允许的最大等待秒数,避免等待时间过长", + ) + wait_duration_multiplier: float = Field( + default=1.0, + ge=0.0, + le=10.0, + description="等待时长倍率,用于整体放大或缩短LLM给出的等待时间", + ) + + class KokoroFlowChatterConfig(ValidatedConfigBase): """ Kokoro Flow Chatter 配置类 - 私聊专用心流对话系统 @@ -947,6 +976,11 @@ class KokoroFlowChatterConfig(ValidatedConfigBase): description="是否在等待期间启用心理活动更新" ) + waiting: KokoroFlowChatterWaitingConfig = Field( + default_factory=KokoroFlowChatterWaitingConfig, + description="等待策略配置(默认等待时间、倍率等)", + ) + # --- 私聊专属主动思考配置 --- proactive_thinking: KokoroFlowChatterProactiveConfig = Field( default_factory=KokoroFlowChatterProactiveConfig, diff --git a/src/memory_graph/models.py b/src/memory_graph/models.py index 6b662b28f..6dd7e5f2c 100644 --- a/src/memory_graph/models.py +++ b/src/memory_graph/models.py @@ -233,6 +233,7 @@ class Memory: activation: float = 0.0 # 激活度 [0-1],用于记忆整合和遗忘 status: MemoryStatus = MemoryStatus.STAGED # 记忆状态 created_at: datetime = field(default_factory=datetime.now) + updated_at: datetime | None = None # 最近一次结构或元数据更新 last_accessed: datetime = field(default_factory=datetime.now) # 最后访问时间 access_count: int = 0 # 访问次数 decay_factor: float = 1.0 # 衰减因子(随时间变化) @@ -245,6 +246,8 @@ class Memory: # 确保重要性和激活度在有效范围内 self.importance = max(0.0, min(1.0, self.importance)) self.activation = max(0.0, min(1.0, self.activation)) + if not self.updated_at: + self.updated_at = self.created_at def to_dict(self) -> dict[str, Any]: """转换为字典(用于序列化)""" @@ -258,6 +261,7 @@ class Memory: "activation": self.activation, "status": self.status.value, "created_at": self.created_at.isoformat(), + "updated_at": self.updated_at.isoformat() if self.updated_at else None, "last_accessed": self.last_accessed.isoformat(), "access_count": self.access_count, "decay_factor": self.decay_factor, @@ -278,6 +282,13 @@ class Memory: # 备选:使用直接的 activation 字段 activation_level = data.get("activation", 0.0) + updated_at_raw = data.get("updated_at") + if updated_at_raw: + updated_at = datetime.fromisoformat(updated_at_raw) + else: + # 旧数据没有 updated_at,退化为最后访问时间或创建时间 + updated_at = datetime.fromisoformat(data.get("last_accessed", data["created_at"])) + return cls( id=data["id"], subject_id=data["subject_id"], @@ -288,6 +299,7 @@ class Memory: activation=activation_level, # 使用统一的激活度值 status=MemoryStatus(data.get("status", "staged")), created_at=datetime.fromisoformat(data["created_at"]), + updated_at=updated_at, last_accessed=datetime.fromisoformat(data.get("last_accessed", data["created_at"])), access_count=data.get("access_count", 0), decay_factor=data.get("decay_factor", 1.0), diff --git a/src/plugins/built_in/kokoro_flow_chatter/chatter.py b/src/plugins/built_in/kokoro_flow_chatter/chatter.py index 5b558c75c..60bf5349f 100644 --- a/src/plugins/built_in/kokoro_flow_chatter/chatter.py +++ b/src/plugins/built_in/kokoro_flow_chatter/chatter.py @@ -26,7 +26,7 @@ from src.common.logger import get_logger from src.plugin_system.base.base_chatter import BaseChatter from src.plugin_system.base.component_types import ChatType -from .config import KFCMode, get_config +from .config import KFCMode, apply_wait_duration_rules, get_config from .models import SessionStatus from .session import get_session_manager @@ -179,6 +179,15 @@ class KokoroFlowChatter(BaseChatter): ) # 10. 执行动作 + adjusted_wait = apply_wait_duration_rules(plan_response.max_wait_seconds) + if adjusted_wait != plan_response.max_wait_seconds: + logger.debug( + "[KFC] 调整等待时长: raw=%ss adjusted=%ss", + plan_response.max_wait_seconds, + adjusted_wait, + ) + plan_response.max_wait_seconds = adjusted_wait + exec_results = [] has_reply = False diff --git a/src/plugins/built_in/kokoro_flow_chatter/config.py b/src/plugins/built_in/kokoro_flow_chatter/config.py index cba8669a4..95d8dc0fd 100644 --- a/src/plugins/built_in/kokoro_flow_chatter/config.py +++ b/src/plugins/built_in/kokoro_flow_chatter/config.py @@ -48,6 +48,9 @@ class WaitingDefaults: # 最大等待时间 max_wait_seconds: int = 1800 + # 等待时长倍率(>1 放大等待时间,<1 缩短) + wait_duration_multiplier: float = 1.0 + @dataclass class ProactiveConfig: @@ -202,6 +205,7 @@ def load_config() -> KokoroFlowChatterConfig: default_max_wait_seconds=getattr(wait_cfg, 'default_max_wait_seconds', 300), min_wait_seconds=getattr(wait_cfg, 'min_wait_seconds', 30), max_wait_seconds=getattr(wait_cfg, 'max_wait_seconds', 1800), + wait_duration_multiplier=getattr(wait_cfg, 'wait_duration_multiplier', 1.0), ) # 主动思考配置 - 支持 proactive 和 proactive_thinking 两种写法 @@ -262,3 +266,29 @@ def reload_config() -> KokoroFlowChatterConfig: global _config _config = load_config() return _config + + +def apply_wait_duration_rules(raw_wait_seconds: int) -> int: + """根据配置计算最终的等待时间""" + if raw_wait_seconds <= 0: + return 0 + + waiting_cfg = get_config().waiting + multiplier = max(waiting_cfg.wait_duration_multiplier, 0.0) + if multiplier == 0: + return 0 + + adjusted = int(round(raw_wait_seconds * multiplier)) + + min_wait = max(0, waiting_cfg.min_wait_seconds) + max_wait = max(waiting_cfg.max_wait_seconds, 0) + + if max_wait > 0 and min_wait > 0 and max_wait < min_wait: + max_wait = min_wait + + if max_wait > 0: + adjusted = min(adjusted, max_wait) + if min_wait > 0: + adjusted = max(adjusted, min_wait) + + return max(adjusted, 0) diff --git a/src/plugins/built_in/kokoro_flow_chatter/proactive_thinker.py b/src/plugins/built_in/kokoro_flow_chatter/proactive_thinker.py index bfb4c382a..ef10c9cd4 100644 --- a/src/plugins/built_in/kokoro_flow_chatter/proactive_thinker.py +++ b/src/plugins/built_in/kokoro_flow_chatter/proactive_thinker.py @@ -24,7 +24,7 @@ from src.common.logger import get_logger from src.config.config import global_config from src.plugin_system.apis.unified_scheduler import TriggerType, unified_scheduler -from .config import KFCMode, get_config +from .config import KFCMode, apply_wait_duration_rules, get_config from .models import EventType, SessionStatus from .session import KokoroSession, get_session_manager @@ -460,6 +460,15 @@ class ProactiveThinker: action.params["thought"] = plan_response.thought action.params["situation_type"] = "timeout" action.params["extra_context"] = extra_context + + adjusted_wait = apply_wait_duration_rules(plan_response.max_wait_seconds) + if adjusted_wait != plan_response.max_wait_seconds: + logger.debug( + "[ProactiveThinker] 调整超时等待: raw=%ss adjusted=%ss", + plan_response.max_wait_seconds, + adjusted_wait, + ) + plan_response.max_wait_seconds = adjusted_wait # ★ 在执行动作前最后一次检查状态,防止与 Chatter 并发 if session.status != SessionStatus.WAITING: @@ -683,6 +692,15 @@ class ProactiveThinker: action.params["thought"] = plan_response.thought action.params["situation_type"] = "proactive" action.params["extra_context"] = extra_context + + adjusted_wait = apply_wait_duration_rules(plan_response.max_wait_seconds) + if adjusted_wait != plan_response.max_wait_seconds: + logger.debug( + "[ProactiveThinker] 调整主动等待: raw=%ss adjusted=%ss", + plan_response.max_wait_seconds, + adjusted_wait, + ) + plan_response.max_wait_seconds = adjusted_wait # 执行动作(回复生成在 Action.execute() 中完成) for action in plan_response.actions: diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 3593bfe9a..b36e6aa46 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "7.9.6" +version = "7.9.7" #----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读---- #如果你想要修改配置文件,请递增version的值 @@ -622,6 +622,13 @@ mode = "split" max_wait_seconds_default = 300 # 默认的最大等待秒数(AI发送消息后愿意等待用户回复的时间) enable_continuous_thinking = true # 是否在等待期间启用心理活动更新 +# --- 等待策略 --- +[kokoro_flow_chatter.waiting] +default_max_wait_seconds = 300 # LLM 未给出等待时间时的默认值 +min_wait_seconds = 30 # 允许的最短等待时间,防止太快打扰用户 +max_wait_seconds = 1800 # 允许的最长等待时间(秒) +wait_duration_multiplier = 1.0 # 对 LLM 给出的等待时间应用的倍率(>1 放大,<1 缩短) + # --- 私聊专属主动思考配置 --- # 注意:这是KFC专属的主动思考配置,只有当KFC启用时才生效。 # 它旨在模拟更真实、情感驱动的互动,而非简单的定时任务。