diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index cd0ca8317..d672708f3 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -615,7 +615,7 @@ class DefaultReplyer: # 使用统一管理器的智能检索(Judge模型决策) search_result = await unified_manager.search_memories( query_text=query_text, - use_judge=True, + use_judge=global_config.memory.use_judge, recent_chat_history=chat_history, # 传递最近聊天历史 ) diff --git a/src/config/official_configs.py b/src/config/official_configs.py index e2b869af7..e3b7e2c41 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -491,6 +491,7 @@ class MemoryConfig(ValidatedConfigBase): short_term_decay_factor: float = Field(default=0.98, description="衰减因子") # 长期记忆层配置 + use_judge: bool = Field(default=True, description="使用评判模型决定是否检索长期记忆") long_term_batch_size: int = Field(default=10, description="批量转移大小") long_term_decay_factor: float = Field(default=0.95, description="衰减因子") long_term_auto_transfer_interval: int = Field(default=60, description="自动转移间隔(秒)") @@ -876,30 +877,30 @@ class ProactiveThinkingConfig(ValidatedConfigBase): class KokoroFlowChatterProactiveConfig(ValidatedConfigBase): """ Kokoro Flow Chatter 主动思考子配置 - + 设计哲学:主动行为源于内部状态和外部环境的自然反应,而非机械的限制。 她的主动是因为挂念、因为关心、因为想问候,而不是因为"任务"。 """ enabled: bool = Field(default=True, description="是否启用KFC的私聊主动思考") - + # 1. 沉默触发器:当感到长久的沉默时,她可能会想说些什么 silence_threshold_seconds: int = Field( default=7200, ge=60, le=86400, description="用户沉默超过此时长(秒),可能触发主动思考(默认2小时)" ) - + # 2. 关系门槛:她不会对不熟悉的人过于主动 min_affinity_for_proactive: float = Field( default=0.3, ge=0.0, le=1.0, description="需要达到最低好感度,她才会开始主动关心" ) - + # 3. 频率呼吸:为了避免打扰,她的关心总是有间隔的 min_interval_between_proactive: int = Field( default=1800, ge=0, description="两次主动思考之间的最小间隔(秒,默认30分钟)" ) - + # 4. 自然问候:在特定的时间,她会像朋友一样送上问候 enable_morning_greeting: bool = Field( default=True, description="是否启用早安问候 (例如: 8:00 - 9:00)" @@ -907,7 +908,7 @@ class KokoroFlowChatterProactiveConfig(ValidatedConfigBase): enable_night_greeting: bool = Field( default=True, description="是否启用晚安问候 (例如: 22:00 - 23:00)" ) - + # 5. 勿扰时段:在这段时间内不会主动发起对话 quiet_hours_start: str = Field( default="23:00", description="勿扰时段开始时间,格式: HH:MM" @@ -915,7 +916,7 @@ class KokoroFlowChatterProactiveConfig(ValidatedConfigBase): quiet_hours_end: str = Field( default="07:00", description="勿扰时段结束时间,格式: HH:MM" ) - + # 6. 触发概率:每次检查时主动发起的概率 trigger_probability: float = Field( default=0.3, ge=0.0, le=1.0, @@ -961,14 +962,14 @@ class KokoroFlowChatterWaitingConfig(ValidatedConfigBase): class KokoroFlowChatterConfig(ValidatedConfigBase): """ Kokoro Flow Chatter 配置类 - 私聊专用心流对话系统 - + 设计理念:KFC不是独立人格,它复用全局的人设、情感框架和回复模型, 只作为Bot核心人格在私聊中的一种特殊表现模式。 """ # --- 总开关 --- enable: bool = Field( - default=True, + default=True, description="开启后KFC将接管所有私聊消息;关闭后私聊消息将由AFC处理" ) @@ -978,7 +979,7 @@ class KokoroFlowChatterConfig(ValidatedConfigBase): description="默认的最大等待秒数(AI发送消息后愿意等待用户回复的时间)" ) enable_continuous_thinking: bool = Field( - default=True, + default=True, description="是否在等待期间启用心理活动更新" ) diff --git a/src/plugins/built_in/kokoro_flow_chatter/context_builder.py b/src/plugins/built_in/kokoro_flow_chatter/context_builder.py index b57f06f42..816bc6005 100644 --- a/src/plugins/built_in/kokoro_flow_chatter/context_builder.py +++ b/src/plugins/built_in/kokoro_flow_chatter/context_builder.py @@ -36,16 +36,16 @@ def _get_config(): class KFCContextBuilder: """ KFC V2 上下文构建器 - + 为提示词提供完整的情境感知数据。 """ - + def __init__(self, chat_stream: "ChatStream"): self.chat_stream = chat_stream self.chat_id = chat_stream.stream_id self.platform = chat_stream.platform self.is_group_chat = bool(chat_stream.group_info) - + async def build_all_context( self, sender_name: str, @@ -56,21 +56,21 @@ class KFCContextBuilder: ) -> dict[str, str]: """ 并行构建所有上下文模块 - + Args: sender_name: 发送者名称 target_message: 目标消息内容 context: 聊天流上下文(可选) user_id: 用户ID(可选,用于精确查找关系信息) enable_tool: 是否启用工具调用 - + Returns: dict: 包含所有上下文块的字典 """ logger.debug(f"[KFC上下文] 开始构建上下文: sender={sender_name}, target={target_message[:50] if target_message else '(空)'}...") - + chat_history = await self._get_chat_history_text(context) - + tasks = { "relation_info": self._build_relation_info(sender_name, target_message, user_id), "memory_block": self._build_memory_block(chat_history, target_message, context), @@ -79,10 +79,10 @@ class KFCContextBuilder: "schedule": self._build_schedule_block(), "time": self._build_time_block(), } - + results = {} timing_logs = [] - + # 任务名称中英文映射 task_name_mapping = { "relation_info": "感受关系", @@ -92,13 +92,13 @@ class KFCContextBuilder: "schedule": "日程", "time": "时间", } - + try: task_results = await asyncio.gather( *[self._wrap_task_with_timing(name, coro) for name, coro in tasks.items()], return_exceptions=True ) - + for result in task_results: if isinstance(result, tuple) and len(result) == 3: name, value, duration = result @@ -111,13 +111,13 @@ class KFCContextBuilder: logger.warning(f"上下文构建任务异常: {result}") except Exception as e: logger.error(f"并行构建上下文失败: {e}") - + # 输出耗时日志 if timing_logs: logger.info(f"在回复前的步骤耗时: {'; '.join(timing_logs)}") - + return results - + async def _wrap_task_with_timing(self, name: str, coro) -> tuple[str, str, float]: """包装任务以返回名称、结果和耗时""" start_time = time.time() @@ -129,7 +129,7 @@ class KFCContextBuilder: duration = time.time() - start_time logger.error(f"构建 {name} 失败: {e}") return (name, "", duration) - + async def _get_chat_history_text( self, context: Optional["StreamContext"] = None, @@ -138,16 +138,16 @@ class KFCContextBuilder: """获取聊天历史文本""" if context is None: return "" - + try: from src.chat.utils.chat_message_builder import build_readable_messages - + messages = context.get_messages(limit=limit, include_unread=True) if not messages: return "" - + msg_dicts = [msg.flatten() for msg in messages] - + return await build_readable_messages( msg_dicts, replace_bot_name=True, @@ -157,54 +157,54 @@ class KFCContextBuilder: except Exception as e: logger.error(f"获取聊天历史失败: {e}") return "" - + async def _build_relation_info(self, sender_name: str, target_message: str, user_id: Optional[str] = None) -> str: """构建关系信息块""" config = _get_config() - + if sender_name == f"{config.bot.nickname}(你)": return "你将要回复的是你自己发送的消息。" - + person_info_manager = get_person_info_manager() - + # 优先使用 user_id + platform 获取 person_id person_id = None if user_id and self.platform: person_id = person_info_manager.get_person_id(self.platform, user_id) logger.debug(f"通过 platform={self.platform}, user_id={user_id} 获取 person_id={person_id}") - + # 如果没有找到,尝试通过 person_name 查找 if not person_id: person_id = await person_info_manager.get_person_id_by_person_name(sender_name) - + if not person_id: logger.debug(f"未找到用户 {sender_name} 的 person_id") return f"你与{sender_name}还没有建立深厚的关系,这是早期的互动阶段。" - + try: from src.person_info.relationship_fetcher import relationship_fetcher_manager - + relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_id) - + user_relation_info = await relationship_fetcher.build_relation_info(person_id, points_num=5) stream_impression = await relationship_fetcher.build_chat_stream_impression(self.chat_id) - + parts = [] if user_relation_info: parts.append(f"### 你与 {sender_name} 的关系\n{user_relation_info}") if stream_impression: scene_type = "这个群" if self.is_group_chat else "你们的私聊" parts.append(f"### 你对{scene_type}的印象\n{stream_impression}") - + if parts: return "\n\n".join(parts) else: return f"你与{sender_name}还没有建立深厚的关系,这是早期的互动阶段。" - + except Exception as e: logger.error(f"获取关系信息失败: {e}") return f"你与{sender_name}是普通朋友关系。" - + async def _build_memory_block( self, chat_history: str, @@ -213,44 +213,44 @@ class KFCContextBuilder: ) -> str: """构建记忆块(使用三层记忆系统)""" config = _get_config() - + if not (config.memory and config.memory.enable): logger.debug("[KFC记忆] 记忆系统未启用") return "" - + try: from src.memory_graph.manager_singleton import get_unified_memory_manager from src.memory_graph.utils.three_tier_formatter import memory_formatter - + unified_manager = get_unified_memory_manager() if not unified_manager: logger.warning("[KFC记忆] 管理器未初始化,跳过记忆检索") return "" - + # 构建查询文本(使用最近多条消息的组合块) query_text = self._build_memory_query_text(target_message, context) logger.debug(f"[KFC记忆] 开始检索,查询文本: {query_text[:100]}...") - + search_result = await unified_manager.search_memories( query_text=query_text, - use_judge=True, + use_judge=config.memory.use_judge, recent_chat_history=chat_history, ) - + if not search_result: logger.debug("[KFC记忆] 未找到相关记忆") return "" - + perceptual_blocks = search_result.get("perceptual_blocks", []) short_term_memories = search_result.get("short_term_memories", []) long_term_memories = search_result.get("long_term_memories", []) - + formatted_memories = await memory_formatter.format_all_tiers( perceptual_blocks=perceptual_blocks, short_term_memories=short_term_memories, long_term_memories=long_term_memories ) - + total_count = len(perceptual_blocks) + len(short_term_memories) + len(long_term_memories) if total_count > 0 and formatted_memories.strip(): logger.info( @@ -258,16 +258,16 @@ class KFCContextBuilder: f"(感知:{len(perceptual_blocks)}, 短期:{len(short_term_memories)}, 长期:{len(long_term_memories)})" ) return f"### 🧠 相关记忆\n\n{formatted_memories}" - + logger.debug("[KFC记忆] 记忆为空") return "" - + except Exception as e: logger.error(f"[KFC记忆] 检索失败: {e}") import traceback traceback.print_exc() return "" - + def _build_memory_query_text( self, fallback_text: str, @@ -276,23 +276,23 @@ class KFCContextBuilder: ) -> str: """ 将最近若干条消息拼接为一个查询块,用于生成语义向量。 - + Args: fallback_text: 如果无法拼接消息块时使用的后备文本 context: 聊天流上下文 block_size: 组合的消息数量 - + Returns: str: 用于检索的查询文本 """ if not context: return fallback_text - + try: messages = context.get_messages(limit=block_size, include_unread=True) if not messages: return fallback_text - + lines = [] for msg in messages: sender = "" @@ -303,11 +303,11 @@ class KFCContextBuilder: lines.append(f"{sender}: {content}") elif content: lines.append(content) - + return "\n".join(lines) if lines else fallback_text except Exception: return fallback_text - + async def _build_tool_info( self, chat_history: str, @@ -316,30 +316,30 @@ class KFCContextBuilder: enable_tool: bool = True, ) -> str: """构建工具信息块 - + Args: chat_history: 聊天历史记录 sender_name: 发送者名称 target_message: 目标消息内容 enable_tool: 是否启用工具调用 - + Returns: str: 工具信息字符串 """ if not enable_tool: return "" - + try: from src.plugin_system.core.tool_use import ToolExecutor - + tool_executor = ToolExecutor(chat_id=self.chat_id) - + info_parts = [] - + # ========== 1. 主动召回联网搜索缓存 ========== try: from src.common.cache_manager import tool_cache - + # 使用聊天历史作为语义查询 query_text = chat_history if chat_history else target_message recalled_caches = await tool_cache.recall_relevant_cache( @@ -348,7 +348,7 @@ class KFCContextBuilder: top_k=2, similarity_threshold=0.65, # 相似度阈值 ) - + if recalled_caches: recall_parts = ["### 🔍 相关的历史搜索结果"] for item in recalled_caches: @@ -360,19 +360,19 @@ class KFCContextBuilder: if len(content) > 500: content = content[:500] + "..." recall_parts.append(f"**搜索「{original_query}」** (相关度:{similarity:.0%})\n{content}") - + info_parts.append("\n\n".join(recall_parts)) logger.info(f"[缓存召回] 召回了 {len(recalled_caches)} 条相关搜索缓存") except Exception as e: logger.debug(f"[缓存召回] 召回失败(非关键): {e}") - + # ========== 2. 获取工具调用历史 ========== tool_history_str = tool_executor.history_manager.format_for_prompt( max_records=3, include_results=True ) if tool_history_str: info_parts.append(tool_history_str) - + # ========== 3. 执行工具调用 ========== tool_results, _, _ = await tool_executor.execute_from_chat_message( sender=sender_name, @@ -380,7 +380,7 @@ class KFCContextBuilder: chat_history=chat_history, return_details=False, ) - + # 显示当前工具调用的结果(简要信息) if tool_results: current_results_parts = ["### 🔧 刚获取的工具信息"] @@ -389,35 +389,35 @@ class KFCContextBuilder: content = tool_result.get("content", "") # 不进行截断,让工具自己处理结果长度 current_results_parts.append(f"- **{tool_name}**: {content}") - + info_parts.append("\n".join(current_results_parts)) logger.info(f"[工具调用] 获取到 {len(tool_results)} 个工具结果") - + # 如果没有任何信息,返回空字符串 if not info_parts: logger.debug("[工具调用] 未获取到任何工具结果或历史记录") return "" - + return "\n\n".join(info_parts) - + except Exception as e: logger.error(f"[工具调用] 工具信息获取失败: {e}") return "" - + async def _build_expression_habits(self, chat_history: str, target_message: str) -> str: """构建表达习惯块""" config = _get_config() - + use_expression, _, _ = config.expression.get_expression_config_for_chat(self.chat_id) if not use_expression: return "" - + try: from src.chat.express.expression_selector import expression_selector - + style_habits = [] grammar_habits = [] - + selected_expressions = await expression_selector.select_suitable_expressions( chat_id=self.chat_id, chat_history=chat_history, @@ -425,7 +425,7 @@ class KFCContextBuilder: max_num=8, min_num=2 ) - + if selected_expressions: for expr in selected_expressions: if isinstance(expr, dict) and "situation" in expr and "style" in expr: @@ -435,40 +435,40 @@ class KFCContextBuilder: grammar_habits.append(habit_str) else: style_habits.append(habit_str) - + parts = [] if style_habits: parts.append("**语言风格习惯**:\n" + "\n".join(f"- {h}" for h in style_habits)) if grammar_habits: parts.append("**句法习惯**:\n" + "\n".join(f"- {h}" for h in grammar_habits)) - + if parts: return "### 💬 你的表达习惯\n\n" + "\n\n".join(parts) - + return "" - + except Exception as e: logger.error(f"构建表达习惯失败: {e}") return "" - + async def _build_schedule_block(self) -> str: """构建日程信息块""" config = _get_config() - + if not config.planning_system.schedule_enable: return "" - + try: from src.schedule.schedule_manager import schedule_manager - + activity_info = schedule_manager.get_current_activity() if not activity_info: return "" - + activity = activity_info.get("activity") time_range = activity_info.get("time_range") now = datetime.now() - + if time_range: try: start_str, end_str = time_range.split("-") @@ -478,15 +478,15 @@ class KFCContextBuilder: end_time = datetime.strptime(end_str.strip(), "%H:%M").replace( year=now.year, month=now.month, day=now.day ) - + if end_time < start_time: end_time += timedelta(days=1) if now < start_time: now += timedelta(days=1) - + duration_minutes = (now - start_time).total_seconds() / 60 remaining_minutes = (end_time - now).total_seconds() / 60 - + return ( f"你当前正在「{activity}」," f"从{start_time.strftime('%H:%M')}开始,预计{end_time.strftime('%H:%M')}结束," @@ -494,13 +494,13 @@ class KFCContextBuilder: ) except (ValueError, AttributeError): pass - + return f"你当前正在「{activity}」" - + except Exception as e: logger.error(f"构建日程块失败: {e}") return "" - + async def _build_time_block(self) -> str: """构建时间信息块""" now = datetime.now() diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 110115ae5..0830fc7f8 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "7.9.8" +version = "7.9.9" #----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读---- #如果你想要修改配置文件,请递增version的值 @@ -89,7 +89,7 @@ command_prefixes = ['/'] [personality] # 建议50字以内,描述人格的核心特质 -personality_core = "是一个积极向上的女大学生" +personality_core = "是一个积极向上的女大学生" # 人格的细节,描述人格的一些侧面 personality_side = "用一句话或几句话描述人格的侧面特质" #アイデンティティがない 生まれないらららら @@ -297,6 +297,7 @@ short_term_search_top_k = 5 # 搜索时返回的最大数量 short_term_decay_factor = 0.98 # 衰减因子 # 长期记忆层配置 +use_judge = true # 使用评判模型决定是否检索长期记忆 long_term_batch_size = 10 # 批量转移大小 long_term_decay_factor = 0.95 # 衰减因子 long_term_auto_transfer_interval = 180 # 自动转移间隔(秒) @@ -411,7 +412,7 @@ auto_install = true #it can work now! auto_install_timeout = 300 # 是否使用PyPI镜像源(推荐,可加速下载) use_mirror = true -mirror_url = "https://pypi.tuna.tsinghua.edu.cn/simple" # PyPI镜像源URL,如: "https://pypi.tuna.tsinghua.edu.cn/simple" +mirror_url = "https://pypi.tuna.tsinghua.edu.cn/simple" # PyPI镜像源URL,如: "https://pypi.tuna.tsinghua.edu.cn/simple" # 依赖安装日志级别 install_log_level = "INFO"