diff --git a/changelogs/changelog.md b/changelogs/changelog.md index 461bd7bb..a0b39a62 100644 --- a/changelogs/changelog.md +++ b/changelogs/changelog.md @@ -1,12 +1,10 @@ # Changelog ## [0.7.0] -2025-6-1 -- 重构数据库,弃用MongoDB,采用轻量sqlite,无需额外安装 -- 重构HFC,可扩展的聊天模式,支持独立的表达模式 -- HFC,丰富HFC的决策信息,更好的把握聊天内容 -- HFC初步支持插件v0.1(测试版) -- 重构表情包模块 -- 移除日程系统 +- 你可以选择normal,focus和auto多种不同的聊天方式。normal提供更少的消耗,更快的回复速度。focus提供更好的聊天理解,更多工具使用和插件能力 +- 现在,你可以自定义麦麦的表达方式,并且麦麦也可以学习群友的聊天风格(需要在配置文件中打开) +- 不再需要繁琐的安装MongoDB!弃用MongoDB,采用轻量sqlite,无需额外安装(提供数据迁移脚本) +- focus模式初步支持了插件,我们提供了两个示例插件(需要手动启用),可以让麦麦实现更丰富的操作。禁言插件和豆包绘图插件是示例用插件。 **重构专注聊天(HFC - focus_chat)** - 模块化设计,可以自定义不同的部件 @@ -30,10 +28,14 @@ - 为专注模式添加关系线索 - 在专注模式下,麦麦可以决定自行发送语音消息(需要搭配tts适配器) - 优化reply,减少复读 +- 可自定义连续回复次数 +- 可自定义处理器超时时间 **优化普通聊天(normal_chat)** +- 添加可学习的表达方式 - 增加了talk_frequency参数来有效控制回复频率 - 优化了进入和离开normal_chat的方式 +- 添加时间信息 **新增表达方式学习** - 麦麦配置单独表达方式 @@ -48,7 +50,6 @@ - 移除聊天限额数量 **插件系统** -- 添加示例插件 - 示例插件:禁言插件 - 示例插件:豆包绘图插件 @@ -65,6 +66,9 @@ - 移除日程系统,减少幻觉(将会在未来版本回归) - 移除主心流思考和LLM进入聊天判定 - 支持qwen3模型,支持自定义是否思考和思考长度 +- 优化提及和at的判定 +- 添加配置项 +- 添加临时配置文件读取器 ## [0.6.3-fix-4] - 2025-5-18 diff --git a/scripts/070configexe.py b/scripts/070configexe.py index eeba7d63..be1f56e4 100644 --- a/scripts/070configexe.py +++ b/scripts/070configexe.py @@ -403,15 +403,15 @@ class ConfigEditor: # 创建模型名称标签(大字体) model_name = var.get() if var.get() else providers[0] section_translations = { - "model.utils": "工具模型", - "model.utils_small": "小型工具模型", + "model.utils": "麦麦组件模型", + "model.utils_small": "小型麦麦组件模型", "model.memory_summary": "记忆概括模型", "model.vlm": "图像识别模型", "model.embedding": "嵌入模型", "model.normal_chat_1": "普通聊天:主要聊天模型", "model.normal_chat_2": "普通聊天:次要聊天模型", "model.focus_working_memory": "专注模式:工作记忆模型", - "model.focus_chat_mind": "专注模式:聊天规划模型", + "model.focus_chat_mind": "专注模式:聊天思考模型", "model.focus_tool_use": "专注模式:工具调用模型", "model.focus_planner": "专注模式:决策模型", "model.focus_expressor": "专注模式:表达器模型", diff --git a/scripts/configexe.toml b/scripts/configexe.toml index 603393f7..a322025f 100644 --- a/scripts/configexe.toml +++ b/scripts/configexe.toml @@ -302,6 +302,15 @@ description = "思考的时间间隔(秒),可以有效减少消耗" name = "连续回复能力" description = "连续回复能力,值越高,麦麦连续回复的概率越高" +[translations.items.parallel_processing] +name = "并行处理" +description = "是否并行处理回忆和处理器阶段,可以节省时间" + +[translations.items.processor_max_time] +name = "处理器最大时间" +description = "处理器最大时间,单位秒,如果超过这个时间,处理器会自动停止" + + [translations.items.observation_context_size] name = "观察上下文大小" description = "观察到的最长上下文大小,建议15,太短太长都会导致脑袋尖尖" diff --git a/src/chat/focus_chat/expressors/default_expressor.py b/src/chat/focus_chat/expressors/default_expressor.py index d8cc1064..2d8cf123 100644 --- a/src/chat/focus_chat/expressors/default_expressor.py +++ b/src/chat/focus_chat/expressors/default_expressor.py @@ -41,8 +41,8 @@ def init_prompt(): 你需要使用合适的语法和句法,参考聊天内容,组织一条日常且口语化的回复。 请你根据情景使用以下句法: {grammar_habbits} -回复尽量简短一些。可以参考贴吧,知乎和微博的回复风格,你可以完全重组回复,保留最基本的表达含义就好,但注意回复要简短,但重组后保持语意通顺。 -回复不要浮夸,不要用夸张修辞,平淡一些。不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 ),只输出一条回复就好。 +{config_expression_style},你可以完全重组回复,保留最基本的表达含义就好,但重组后保持语意通顺。 +不要浮夸,不要夸张修辞,平淡且不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 ),只输出一条回复就好。 现在,你说: """, "default_expressor_prompt", @@ -63,8 +63,8 @@ def init_prompt(): 你需要使用合适的语法和句法,参考聊天内容,组织一条日常且口语化的回复。 请你根据情景使用以下句法: {grammar_habbits} -回复尽量简短一些。可以参考贴吧,知乎和微博的回复风格,你可以完全重组回复,保留最基本的表达含义就好,但注意回复要简短,但重组后保持语意通顺。 -回复不要浮夸,不要用夸张修辞,平淡一些。不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 ),只输出一条回复就好。 +{config_expression_style},你可以完全重组回复,保留最基本的表达含义就好,但重组后保持语意通顺。 +不要浮夸,不要夸张修辞,平淡且不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 ),只输出一条回复就好。 现在,你说: """, "default_expressor_private_prompt", # New template for private FOCUSED chat @@ -128,7 +128,7 @@ class DefaultExpressor: # 创建思考消息 await self._create_thinking_message(anchor_message, thinking_id) - reply = None # 初始化 reply,防止未定义 + reply = [] # 初始化 reply,防止未定义 try: has_sent_something = False @@ -216,6 +216,7 @@ class DefaultExpressor: reason=reason, sender_name=sender_name_for_prompt, # Pass determined name target_message=target_message, + config_expression_style=global_config.expression.expression_style, ) # 4. 调用 LLM 生成回复 @@ -230,7 +231,7 @@ class DefaultExpressor: with Timer("LLM生成", {}): # 内部计时器,可选保留 # TODO: API-Adapter修改标记 # logger.info(f"{self.log_prefix}[Replier-{thinking_id}]\nPrompt:\n{prompt}\n") - content, reasoning_content, model_name = await self.express_model.generate_response(prompt) + content, (reasoning_content, model_name) = await self.express_model.generate_response_async(prompt) # logger.info(f"{self.log_prefix}\nPrompt:\n{prompt}\n---------------------------\n") @@ -275,6 +276,7 @@ class DefaultExpressor: sender_name, in_mind_reply, target_message, + config_expression_style, ) -> str: is_group_chat = bool(chat_stream.group_info) @@ -343,6 +345,7 @@ class DefaultExpressor: reason=reason, in_mind_reply=in_mind_reply, target_message=target_message, + config_expression_style=config_expression_style, ) else: # Private chat template_name = "default_expressor_private_prompt" @@ -358,6 +361,7 @@ class DefaultExpressor: reason=reason, in_mind_reply=in_mind_reply, target_message=target_message, + config_expression_style=config_expression_style, ) return prompt diff --git a/src/chat/focus_chat/expressors/exprssion_learner.py b/src/chat/focus_chat/expressors/exprssion_learner.py index 817a188f..afee74af 100644 --- a/src/chat/focus_chat/expressors/exprssion_learner.py +++ b/src/chat/focus_chat/expressors/exprssion_learner.py @@ -19,11 +19,13 @@ def init_prompt() -> None: learn_style_prompt = """ {chat_str} -请从上面这段群聊中概括除了人名为"SELF"之外的人的语言风格,只考虑文字,不要考虑表情包和图片 -不要涉及具体的人名,只考虑语言风格 -语言风格包含特殊内容和情感 -思考有没有特殊的梗,一并总结成语言风格 -总结成如下格式的规律,总结的内容要详细,但具有概括性: +请从上面这段群聊中概括除了人名为"SELF"之外的人的语言风格 +1. 只考虑文字,不要考虑表情包和图片 +2. 不要涉及具体的人名,只考虑语言风格 +3. 语言风格包含特殊内容和情感 +4. 思考有没有特殊的梗,一并总结成语言风格 +5. 例子仅供参考,请严格根据群聊内容总结!!! +注意:总结成如下格式的规律,总结的内容要详细,但具有概括性: 当"xxx"时,可以"xxx", xxx不超过10个字 例如: @@ -31,7 +33,7 @@ def init_prompt() -> None: 当"表示讽刺的赞同,不想讲道理"时,使用"对对对" 当"想说明某个观点,但懒得明说",使用"懂的都懂" -注意不要总结你自己的发言 +注意不要总结你自己(SELF)的发言 现在请你概括 """ Prompt(learn_style_prompt, "learn_style_prompt") @@ -40,9 +42,10 @@ def init_prompt() -> None: {chat_str} 请从上面这段群聊中概括除了人名为"SELF"之外的人的语法和句法特点,只考虑纯文字,不要考虑表情包和图片 -不要总结【图片】,【动画表情】,[图片],[动画表情],不总结 表情符号 at @ 回复 和[回复] -不要涉及具体的人名,只考虑语法和句法特点, -语法和句法特点要包括,句子长短(具体字数),有何种语病,如何拆分句子。 +1.不要总结【图片】,【动画表情】,[图片],[动画表情],不总结 表情符号 at @ 回复 和[回复] +2.不要涉及具体的人名,只考虑语法和句法特点, +3.语法和句法特点要包括,句子长短(具体字数),有何种语病,如何拆分句子。 +4. 例子仅供参考,请严格根据群聊内容总结!!! 总结成如下格式的规律,总结的内容要简洁,不浮夸: 当"xxx"时,可以"xxx" @@ -51,7 +54,7 @@ def init_prompt() -> None: 当"不用详细说明的一般表达"时,使用"非常简洁的句子"的句法 当"需要单纯简单的确认"时,使用"单字或几个字的肯定(1-2个字)"的句法 -注意不要总结你自己的发言 +注意不要总结你自己(SELF)的发言 现在请你概括 """ Prompt(learn_grammar_prompt, "learn_grammar_prompt") diff --git a/src/chat/focus_chat/heartFC_Cycleinfo.py b/src/chat/focus_chat/heartFC_Cycleinfo.py index f1accecd..5a9a52fd 100644 --- a/src/chat/focus_chat/heartFC_Cycleinfo.py +++ b/src/chat/focus_chat/heartFC_Cycleinfo.py @@ -2,12 +2,14 @@ import time import os from typing import List, Optional, Dict, Any +log_dir = "log/log_cycle_debug/" class CycleDetail: """循环信息记录类""" def __init__(self, cycle_id: int): self.cycle_id = cycle_id + self.prefix = "" self.thinking_id = "" self.start_time = time.time() self.end_time: Optional[float] = None @@ -21,21 +23,80 @@ class CycleDetail: def to_dict(self) -> Dict[str, Any]: """将循环信息转换为字典格式""" + def convert_to_serializable(obj, depth=0, seen=None): + if seen is None: + seen = set() + + # 防止递归过深 + if depth > 5: # 降低递归深度限制 + return str(obj) + + # 防止循环引用 + obj_id = id(obj) + if obj_id in seen: + return str(obj) + seen.add(obj_id) + + try: + if hasattr(obj, 'to_dict'): + # 对于有to_dict方法的对象,直接调用其to_dict方法 + return obj.to_dict() + elif isinstance(obj, dict): + # 对于字典,只保留基本类型和可序列化的值 + return {k: convert_to_serializable(v, depth + 1, seen) + for k, v in obj.items() + if isinstance(k, (str, int, float, bool))} + elif isinstance(obj, (list, tuple)): + # 对于列表和元组,只保留可序列化的元素 + return [convert_to_serializable(item, depth + 1, seen) + for item in obj + if not isinstance(item, (dict, list, tuple)) or + isinstance(item, (str, int, float, bool, type(None)))] + elif isinstance(obj, (str, int, float, bool, type(None))): + return obj + else: + return str(obj) + finally: + seen.remove(obj_id) + return { "cycle_id": self.cycle_id, "start_time": self.start_time, "end_time": self.end_time, "timers": self.timers, "thinking_id": self.thinking_id, - "loop_observation_info": self.loop_observation_info, - "loop_process_info": self.loop_process_info, - "loop_plan_info": self.loop_plan_info, - "loop_action_info": self.loop_action_info, + "loop_observation_info": convert_to_serializable(self.loop_observation_info), + "loop_process_info": convert_to_serializable(self.loop_process_info), + "loop_plan_info": convert_to_serializable(self.loop_plan_info), + "loop_action_info": convert_to_serializable(self.loop_action_info), } def complete_cycle(self): """完成循环,记录结束时间""" self.end_time = time.time() + + # 处理 prefix,只保留中英文字符 + if not self.prefix: + self.prefix = "group" + else: + # 只保留中文和英文字符 + self.prefix = ''.join(char for char in self.prefix if '\u4e00' <= char <= '\u9fff' or char.isascii()) + if not self.prefix: + self.prefix = "group" + + current_time_minute = time.strftime("%Y%m%d_%H%M", time.localtime()) + self.log_cycle_to_file(log_dir + self.prefix + f"/{current_time_minute}_cycle_" + str(self.cycle_id) + ".json") + + def log_cycle_to_file(self, file_path: str): + """将循环信息写入文件""" + # 如果目录不存在,则创建目录 + dir_name = os.path.dirname(file_path) + if dir_name and not os.path.exists(dir_name): + os.makedirs(dir_name, exist_ok=True) + # 写入文件 + import json + with open(file_path, "a", encoding="utf-8") as f: + f.write(json.dumps(self.to_dict(), ensure_ascii=False) + "\n") def set_thinking_id(self, thinking_id: str): """设置思考消息ID""" @@ -47,30 +108,3 @@ class CycleDetail: self.loop_processor_info = loop_info["loop_processor_info"] self.loop_plan_info = loop_info["loop_plan_info"] self.loop_action_info = loop_info["loop_action_info"] - - @staticmethod - def list_cycles(stream_id: str, base_dir: str = "log_debug") -> List[str]: - """ - 列出指定stream_id的所有循环文件 - - 参数: - stream_id: 聊天流ID - base_dir: 基础目录,默认为log_debug - - 返回: - List[str]: 文件路径列表 - """ - try: - stream_dir = os.path.join(base_dir, stream_id) - if not os.path.exists(stream_dir): - return [] - - files = [ - os.path.join(stream_dir, f) - for f in os.listdir(stream_dir) - if f.startswith("cycle_") and f.endswith(".txt") - ] - return sorted(files) - except Exception as e: - print(f"列出循环文件时出错: {e}") - return [] diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 3835b2da..c69dea6b 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -44,21 +44,10 @@ PROCESSOR_CLASSES = { "ToolProcessor": (ToolProcessor, "tool_use_processor"), "WorkingMemoryProcessor": (WorkingMemoryProcessor, "working_memory_processor"), "SelfProcessor": (SelfProcessor, "self_identify_processor"), - # "ActionProcessor": (ActionProcessor, "action_processor"), # 这个处理器不需要配置键名,默认启用 } - -WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒 - -EMOJI_SEND_PRO = 0.3 # 设置一个概率,比如 30% 才真的发 - -CONSECUTIVE_NO_REPLY_THRESHOLD = 3 # 连续不回复的阈值 - logger = get_logger("hfc") # Logger Name Changed -# 设定处理器超时时间(秒) -PROCESSOR_TIMEOUT = 30 - async def _handle_cycle_delay(action_taken_this_cycle: bool, cycle_start_time: float, log_prefix: str): """处理循环延迟""" @@ -150,7 +139,7 @@ class HeartFChatting: # 添加循环信息管理相关的属性 self._cycle_counter = 0 self._cycle_history: Deque[CycleDetail] = deque(maxlen=10) # 保留最近10个循环的信息 - self._current_cycle: Optional[CycleDetail] = None + self._current_cycle_detail: Optional[CycleDetail] = None self._shutting_down: bool = False # 关闭标志位 # 存储回调函数 @@ -262,12 +251,12 @@ class HeartFChatting: try: exception = task.exception() if exception: - logger.error(f"{self.log_prefix} HeartFChatting: 麦麦脱离了聊天(异常): {exception}") + logger.error(f"{self.log_prefix} HeartFChatting: 脱离了聊天(异常): {exception}") logger.error(traceback.format_exc()) # Log full traceback for exceptions else: - logger.info(f"{self.log_prefix} HeartFChatting: 麦麦脱离了聊天 (外部停止)") + logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天 (外部停止)") except asyncio.CancelledError: - logger.info(f"{self.log_prefix} HeartFChatting: 麦麦脱离了聊天(任务取消)") + logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天(任务取消)") finally: self._loop_active = False self._loop_task = None @@ -286,7 +275,8 @@ class HeartFChatting: # 创建新的循环信息 self._cycle_counter += 1 - self._current_cycle = CycleDetail(self._cycle_counter) + self._current_cycle_detail = CycleDetail(self._cycle_counter) + self._current_cycle_detail.prefix = self.log_prefix # 初始化周期状态 cycle_timers = {} @@ -295,13 +285,12 @@ class HeartFChatting: # 执行规划和处理阶段 async with self._get_cycle_context(): thinking_id = "tid" + str(round(time.time(), 2)) - self._current_cycle.set_thinking_id(thinking_id) + self._current_cycle_detail.set_thinking_id(thinking_id) # 主循环:思考->决策->执行 async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): logger.debug(f"模板 {self.chat_stream.context.get_template_name()}") loop_info = await self._observe_process_plan_action_loop(cycle_timers, thinking_id) - print(loop_info["loop_action_info"]["command"]) if loop_info["loop_action_info"]["command"] == "stop_focus_chat": logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天") # 如果设置了回调函数,则调用它 @@ -314,10 +303,10 @@ class HeartFChatting: logger.error(traceback.format_exc()) break - self._current_cycle.set_loop_info(loop_info) + self._current_cycle_detail.set_loop_info(loop_info) - self.hfcloop_observation.add_loop_info(self._current_cycle) - self._current_cycle.timers = cycle_timers + self.hfcloop_observation.add_loop_info(self._current_cycle_detail) + self._current_cycle_detail.timers = cycle_timers # 防止循环过快消耗资源 await _handle_cycle_delay( @@ -325,8 +314,8 @@ class HeartFChatting: ) # 完成当前循环并保存历史 - self._current_cycle.complete_cycle() - self._cycle_history.append(self._current_cycle) + self._current_cycle_detail.complete_cycle() + self._cycle_history.append(self._current_cycle_detail) # 记录循环信息和计时器结果 timer_strings = [] @@ -335,7 +324,7 @@ class HeartFChatting: timer_strings.append(f"{name}: {formatted_time}") # 新增:输出每个处理器的耗时 - processor_time_costs = self._current_cycle.loop_processor_info.get("processor_time_costs", {}) + processor_time_costs = self._current_cycle_detail.loop_processor_info.get("processor_time_costs", {}) processor_time_strings = [] for pname, ptime in processor_time_costs.items(): formatted_ptime = f"{ptime * 1000:.2f}毫秒" if ptime < 1 else f"{ptime:.2f}秒" @@ -345,9 +334,9 @@ class HeartFChatting: ) logger.info( - f"{self.log_prefix} 第{self._current_cycle.cycle_id}次思考," - f"耗时: {self._current_cycle.end_time - self._current_cycle.start_time:.1f}秒, " - f"动作: {self._current_cycle.loop_plan_info['action_result']['action_type']}" + f"{self.log_prefix} 第{self._current_cycle_detail.cycle_id}次思考," + f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " + f"动作: {self._current_cycle_detail.loop_plan_info['action_result']['action_type']}" + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") + processor_time_log ) @@ -384,7 +373,7 @@ class HeartFChatting: self._processing_lock.release() async def _process_processors( - self, observations: List[Observation], running_memorys: List[Dict[str, Any]], cycle_timers: dict + self, observations: List[Observation], running_memorys: List[Dict[str, Any]] ) -> tuple[List[InfoBase], Dict[str, float]]: # 记录并行任务开始时间 parallel_start_time = time.time() @@ -400,7 +389,7 @@ class HeartFChatting: async def run_with_timeout(proc=processor): return await asyncio.wait_for( proc.process_info(observations=observations, running_memorys=running_memorys), - timeout=PROCESSOR_TIMEOUT, + timeout=global_config.focus_chat.processor_max_time, ) task = asyncio.create_task(run_with_timeout()) @@ -429,8 +418,8 @@ class HeartFChatting: # 记录耗时 processor_time_costs[processor_name] = duration_since_parallel_start except asyncio.TimeoutError: - logger.info(f"{self.log_prefix} 处理器 {processor_name} 超时(>{PROCESSOR_TIMEOUT}s),已跳过") - processor_time_costs[processor_name] = PROCESSOR_TIMEOUT + logger.info(f"{self.log_prefix} 处理器 {processor_name} 超时(>{global_config.focus_chat.processor_max_time}s),已跳过") + processor_time_costs[processor_name] = global_config.focus_chat.processor_max_time except Exception as e: logger.error( f"{self.log_prefix} 处理器 {processor_name} 执行失败,耗时 (自并行开始): {duration_since_parallel_start:.2f}秒. 错误: {e}", @@ -473,28 +462,42 @@ class HeartFChatting: } self.all_observations = observations - - with Timer("回忆", cycle_timers): - running_memorys = await self.memory_activator.activate_memory(observations) - + with Timer("调整动作", cycle_timers): # 处理特殊的观察 - await self.action_modifier.modify_actions(observations=observations, running_memorys=running_memorys) + await self.action_modifier.modify_actions(observations=observations) await self.action_observation.observe() observations.append(self.action_observation) - with Timer("执行 信息处理器", cycle_timers): - all_plan_info, processor_time_costs = await self._process_processors( - observations, running_memorys, cycle_timers - ) + # 根据配置决定是否并行执行回忆和处理器阶段 + # print(global_config.focus_chat.parallel_processing) + if global_config.focus_chat.parallel_processing: + # 并行执行回忆和处理器阶段 + with Timer("并行回忆和处理", cycle_timers): + memory_task = asyncio.create_task(self.memory_activator.activate_memory(observations)) + processor_task = asyncio.create_task(self._process_processors(observations, [])) + + # 等待两个任务完成 + running_memorys, (all_plan_info, processor_time_costs) = await asyncio.gather(memory_task, processor_task) + else: + # 串行执行 + with Timer("回忆", cycle_timers): + running_memorys = await self.memory_activator.activate_memory(observations) + + with Timer("执行 信息处理器", cycle_timers): + all_plan_info, processor_time_costs = await self._process_processors( + observations, running_memorys + ) + + loop_processor_info = { + "all_plan_info": all_plan_info, + "processor_time_costs": processor_time_costs, + } + - loop_processor_info = { - "all_plan_info": all_plan_info, - "processor_time_costs": processor_time_costs, - } with Timer("规划器", cycle_timers): - plan_result = await self.action_planner.plan(all_plan_info, cycle_timers) + plan_result = await self.action_planner.plan(all_plan_info, running_memorys) loop_plan_info = { "action_result": plan_result.get("action_result", {}), @@ -526,6 +529,7 @@ class HeartFChatting: "action_taken": success, "reply_text": reply_text, "command": command, + "taken_time": time.time(), } loop_info = { diff --git a/src/chat/focus_chat/info_processors/chattinginfo_processor.py b/src/chat/focus_chat/info_processors/chattinginfo_processor.py index ca084d2a..3812a6fd 100644 --- a/src/chat/focus_chat/info_processors/chattinginfo_processor.py +++ b/src/chat/focus_chat/info_processors/chattinginfo_processor.py @@ -104,7 +104,7 @@ class ChattingInfoProcessor(BaseProcessor): if obs.compressor_prompt: summary = "" try: - summary_result, _, _ = await self.model_summary.generate_response(obs.compressor_prompt) + summary_result, _ = await self.model_summary.generate_response_async(obs.compressor_prompt) summary = "没有主题的闲聊" if summary_result: summary = summary_result diff --git a/src/chat/focus_chat/info_processors/mind_processor.py b/src/chat/focus_chat/info_processors/mind_processor.py index 12671169..910b5c75 100644 --- a/src/chat/focus_chat/info_processors/mind_processor.py +++ b/src/chat/focus_chat/info_processors/mind_processor.py @@ -24,9 +24,7 @@ logger = get_logger("processor") def init_prompt(): group_prompt = """ 你的名字是{bot_name} -{memory_str} -{extra_info} -{relation_prompt} +{memory_str}{extra_info}{relation_prompt} {cycle_info_block} 现在是{time_now},你正在上网,和qq群里的网友们聊天,以下是正在进行的聊天内容: {chat_observe_info} @@ -46,15 +44,11 @@ def init_prompt(): private_prompt = """ 你的名字是{bot_name} -{memory_str} -{extra_info} -{relation_prompt} +{memory_str}{extra_info}{relation_prompt} {cycle_info_block} 现在是{time_now},你正在上网,和qq群里的网友们聊天,以下是正在进行的聊天内容: {chat_observe_info} - {action_observe_info} - 以下是你之前对聊天的观察和规划,你的名字是{bot_name}: {last_mind} @@ -77,7 +71,7 @@ class MindProcessor(BaseProcessor): self.llm_model = LLMRequest( model=global_config.model.focus_chat_mind, - temperature=global_config.model.focus_chat_mind["temp"], + # temperature=global_config.model.focus_chat_mind["temp"], max_tokens=800, request_type="focus.processor.chat_mind", ) @@ -155,14 +149,14 @@ class MindProcessor(BaseProcessor): # ---------- 0. 更新和清理 structured_info ---------- if self.structured_info: - updated_info = [] - for item in self.structured_info: - item["ttl"] -= 1 - if item["ttl"] > 0: - updated_info.append(item) - else: - logger.debug(f"{self.log_prefix} 移除过期的 structured_info 项: {item['id']}") - self.structured_info = updated_info + # updated_info = [] + # for item in self.structured_info: + # item["ttl"] -= 1 + # if item["ttl"] > 0: + # updated_info.append(item) + # else: + # logger.debug(f"{self.log_prefix} 移除过期的 structured_info 项: {item['id']}") + # self.structured_info = updated_info self._update_structured_info_str() logger.debug( f"{self.log_prefix} 当前完整的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}" @@ -231,7 +225,7 @@ class MindProcessor(BaseProcessor): # 处理总体异常 logger.error(f"{self.log_prefix} 执行LLM请求或处理响应时出错: {e}") logger.error(traceback.format_exc()) - content = "思考过程中出现错误" + content = "注意:思考过程中出现错误,应该是LLM大模型有问题!!你需要告诉别人,检查大模型配置" # 记录初步思考结果 logger.debug(f"{self.log_prefix} 思考prompt: \n{prompt}\n") diff --git a/src/chat/focus_chat/info_processors/self_processor.py b/src/chat/focus_chat/info_processors/self_processor.py index d489efa2..72562ad5 100644 --- a/src/chat/focus_chat/info_processors/self_processor.py +++ b/src/chat/focus_chat/info_processors/self_processor.py @@ -25,9 +25,7 @@ def init_prompt(): 你是一个AI,但是你伪装成了一个人类,你的人格是,{prompt_personality} {indentify_block} -{relation_prompt} -{memory_str} - +{relation_prompt}{memory_str} 现在是{time_now},你正在上网,和qq群里的网友们聊天,以下是正在进行的聊天内容: {chat_observe_info} diff --git a/src/chat/focus_chat/info_processors/tool_processor.py b/src/chat/focus_chat/info_processors/tool_processor.py index 3c0dc116..46c2657d 100644 --- a/src/chat/focus_chat/info_processors/tool_processor.py +++ b/src/chat/focus_chat/info_processors/tool_processor.py @@ -23,10 +23,7 @@ def init_prompt(): # 添加工具执行器提示词 tool_executor_prompt = """ 你是一个专门执行工具的助手。你的名字是{bot_name}。现在是{time_now}。 - -你当前的额外信息: {memory_str} - 群里正在进行的聊天内容: {chat_observe_info} @@ -165,8 +162,9 @@ class ToolProcessor(BaseProcessor): logger.debug(f"开始执行工具调用{prompt}") response, _, tool_calls = await self.llm_model.generate_response_tool_async(prompt=prompt, tools=tools) - logger.debug(f"获取到工具原始输出:\n{tool_calls}") - # 处理工具调用和结果收集,类似于SubMind中的逻辑 + if tool_calls: + logger.debug(f"获取到工具原始输出:\n{tool_calls}") + # 处理工具调用和结果收集,类似于SubMind中的逻辑 new_structured_items = [] used_tools = [] # 记录使用了哪些工具 diff --git a/src/chat/focus_chat/planners/actions/no_reply_action.py b/src/chat/focus_chat/planners/actions/no_reply_action.py index 1b21a8ce..120ebe98 100644 --- a/src/chat/focus_chat/planners/actions/no_reply_action.py +++ b/src/chat/focus_chat/planners/actions/no_reply_action.py @@ -26,8 +26,8 @@ class NoReplyAction(BaseAction): action_parameters = {} action_require = [ "话题无关/无聊/不感兴趣/不懂", - "最后一条消息是你自己发的且无人回应你", - "你发送了太多消息,且无人回复", + "聊天记录中最新一条消息是你自己发的且无人回应你", + "你连续发送了太多消息,且无人回复", ] default = True diff --git a/src/chat/focus_chat/planners/modify_actions.py b/src/chat/focus_chat/planners/modify_actions.py index 1648e6cf..731fe5f9 100644 --- a/src/chat/focus_chat/planners/modify_actions.py +++ b/src/chat/focus_chat/planners/modify_actions.py @@ -28,11 +28,8 @@ class ActionModifier: async def modify_actions( self, observations: Optional[List[Observation]] = None, - running_memorys: Optional[List[Dict]] = None, **kwargs: Any, ): - # print(f"observations: {observations}") - # processed_infos = [] # 处理Observation对象 if observations: diff --git a/src/chat/focus_chat/planners/planner.py b/src/chat/focus_chat/planners/planner.py index 528f7a7f..a0b0ccf9 100644 --- a/src/chat/focus_chat/planners/planner.py +++ b/src/chat/focus_chat/planners/planner.py @@ -15,6 +15,7 @@ from src.common.logger_manager import get_logger from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.individuality.individuality import individuality from src.chat.focus_chat.planners.action_manager import ActionManager +from json_repair import repair_json logger = get_logger("planner") @@ -26,9 +27,8 @@ def init_prompt(): """ 你的自我认知是: {self_info_block} - {extra_info_block} - +{memory_str} 你需要基于以下信息决定如何参与对话 这些信息可能会有冲突,请你整合这些信息,并选择一个最合适的action: {chat_content_block} @@ -49,7 +49,7 @@ def init_prompt(): 请你以下面格式输出你选择的action: {{ "action": "action_name", - "reasoning": "你的决策理由", + "reasoning": "说明你做出该action的原因", "参数1": "参数1的值", "参数2": "参数2的值", "参数3": "参数3的值", @@ -84,13 +84,13 @@ class ActionPlanner: self.action_manager = action_manager - async def plan(self, all_plan_info: List[InfoBase], cycle_timers: dict) -> Dict[str, Any]: + async def plan(self, all_plan_info: List[InfoBase], running_memorys: List[Dict[str, Any]]) -> Dict[str, Any]: """ 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 参数: all_plan_info: 所有计划信息 - cycle_timers: 计时器字典 + running_memorys: 回忆信息 """ action = "no_reply" # 默认动作 @@ -169,12 +169,15 @@ class ActionPlanner: current_available_actions=current_available_actions, # <-- Pass determined actions cycle_info=cycle_info, # <-- Pass cycle info extra_info=extra_info, + running_memorys=running_memorys, ) # --- 调用 LLM (普通文本生成) --- llm_content = None try: - llm_content, reasoning_content, _ = await self.planner_llm.generate_response(prompt=prompt) + prompt = f"{prompt}" + print(len(prompt)) + llm_content, (reasoning_content, _) = await self.planner_llm.generate_response_async(prompt=prompt) logger.debug(f"{self.log_prefix}[Planner] LLM 原始 JSON 响应 (预期): {llm_content}") logger.debug(f"{self.log_prefix}[Planner] LLM 原始理由 响应 (预期): {reasoning_content}") except Exception as req_e: @@ -184,13 +187,16 @@ class ActionPlanner: if llm_content: try: - # 尝试去除可能的 markdown 代码块标记 - cleaned_content = ( - llm_content.strip().removeprefix("```json").removeprefix("```").removesuffix("```").strip() - ) - if not cleaned_content: - raise json.JSONDecodeError("Cleaned content is empty", cleaned_content, 0) - parsed_json = json.loads(cleaned_content) + fixed_json_string = repair_json(llm_content) + if isinstance(fixed_json_string, str): + try: + parsed_json = json.loads(fixed_json_string) + except json.JSONDecodeError as decode_error: + logger.error(f"JSON解析错误: {str(decode_error)}") + parsed_json = {} + else: + # 如果repair_json直接返回了字典对象,直接使用 + parsed_json = fixed_json_string # 提取决策,提供默认值 extracted_action = parsed_json.get("action", "no_reply") @@ -244,6 +250,7 @@ class ActionPlanner: "action_result": action_result, "current_mind": current_mind, "observed_messages": observed_messages, + "action_prompt": prompt, } return plan_result @@ -259,10 +266,22 @@ class ActionPlanner: current_available_actions: Dict[str, ActionInfo], cycle_info: Optional[str], extra_info: list[str], + running_memorys: List[Dict[str, Any]], ) -> str: """构建 Planner LLM 的提示词 (获取模板并填充数据)""" try: - # --- Determine chat context --- + + memory_str = "" + if global_config.focus_chat.parallel_processing: + memory_str = "" + if running_memorys: + memory_str = "以下是当前在聊天中,你回忆起的记忆:\n" + for running_memory in running_memorys: + memory_str += f"{running_memory['topic']}: {running_memory['content']}\n" + + + + chat_context_description = "你现在正在一个群聊中" chat_target_name = None # Only relevant for private if not is_group_chat and chat_target_info: @@ -324,6 +343,7 @@ class ActionPlanner: planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt") prompt = planner_prompt_template.format( self_info_block=self_info_block, + memory_str=memory_str, # bot_name=global_config.bot.nickname, prompt_personality=personality_block, chat_context_description=chat_context_description, diff --git a/src/chat/heart_flow/observation/actions_observation.py b/src/chat/heart_flow/observation/actions_observation.py index 8310a17b..6f0cd81c 100644 --- a/src/chat/heart_flow/observation/actions_observation.py +++ b/src/chat/heart_flow/observation/actions_observation.py @@ -34,3 +34,13 @@ class ActionObservation: action_info_block += "\n注意,除了上面动作选项之外,你在群聊里不能做其他任何事情,这是你能力的边界\n" self.observe_info = action_info_block + + def to_dict(self) -> dict: + """将观察对象转换为可序列化的字典""" + return { + "observe_info": self.observe_info, + "observe_id": self.observe_id, + "last_observe_time": self.last_observe_time, + "all_actions": self.all_actions, + "all_using_actions": self.all_using_actions + } diff --git a/src/chat/heart_flow/observation/chatting_observation.py b/src/chat/heart_flow/observation/chatting_observation.py index e5c5069e..7e9e562d 100644 --- a/src/chat/heart_flow/observation/chatting_observation.py +++ b/src/chat/heart_flow/observation/chatting_observation.py @@ -66,6 +66,24 @@ class ChattingObservation(Observation): self.oldest_messages_str = "" self.compressor_prompt = "" + def to_dict(self) -> dict: + """将观察对象转换为可序列化的字典""" + return { + "chat_id": self.chat_id, + "platform": self.platform, + "is_group_chat": self.is_group_chat, + "chat_target_info": self.chat_target_info, + "talking_message_str": self.talking_message_str, + "talking_message_str_truncate": self.talking_message_str_truncate, + "name": self.name, + "nick_name": self.nick_name, + "mid_memory_info": self.mid_memory_info, + "person_list": self.person_list, + "oldest_messages_str": self.oldest_messages_str, + "compressor_prompt": self.compressor_prompt, + "last_observe_time": self.last_observe_time + } + async def initialize(self): self.is_group_chat, self.chat_target_info = await get_chat_type_and_target_info(self.chat_id) logger.debug(f"初始化observation: self.is_group_chat: {self.is_group_chat}") diff --git a/src/chat/heart_flow/observation/hfcloop_observation.py b/src/chat/heart_flow/observation/hfcloop_observation.py index 7c782bfd..48bf33ed 100644 --- a/src/chat/heart_flow/observation/hfcloop_observation.py +++ b/src/chat/heart_flow/observation/hfcloop_observation.py @@ -27,40 +27,70 @@ class HFCloopObservation: recent_active_cycles: List[CycleDetail] = [] for cycle in reversed(self.history_loop): # 只关心实际执行了动作的循环 - action_taken = cycle.loop_action_info["action_taken"] - if action_taken: - recent_active_cycles.append(cycle) - if len(recent_active_cycles) == 5: - break + # action_taken = cycle.loop_action_info["action_taken"] + # if action_taken: + recent_active_cycles.append(cycle) + if len(recent_active_cycles) == 5: + break cycle_info_block = "" + action_detailed_str = "" consecutive_text_replies = 0 responses_for_prompt = [] + cycle_last_reason = "" + # 检查这最近的活动循环中有多少是连续的文本回复 (从最近的开始看) for cycle in recent_active_cycles: action_type = cycle.loop_plan_info["action_result"]["action_type"] + action_reasoning = cycle.loop_plan_info["action_result"]["reasoning"] + is_taken = cycle.loop_action_info["action_taken"] + action_taken_time = cycle.loop_action_info["taken_time"] + action_taken_time_str = datetime.fromtimestamp(action_taken_time).strftime("%H:%M:%S") + # print(action_type) + # print(action_reasoning) + # print(is_taken) + # print(action_taken_time_str) + # print("--------------------------------") + if action_reasoning != cycle_last_reason: + cycle_last_reason = action_reasoning + action_reasoning_str = f"你选择这个action的原因是:{action_reasoning}" + else: + action_reasoning_str = "" + if action_type == "reply": consecutive_text_replies += 1 response_text = cycle.loop_plan_info["action_result"]["action_data"].get("text", "[空回复]") responses_for_prompt.append(response_text) + + if is_taken: + action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}')。{action_reasoning_str}\n" + else: + action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}'),但是动作失败了。{action_reasoning_str}\n" + elif action_type == "no_reply": + action_detailed_str += f"{action_taken_time_str}时,你选择不回复(action:{action_type}),{action_reasoning_str}\n" else: - break - + if is_taken: + action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}),{action_reasoning_str}\n" + else: + action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}),但是动作失败了。{action_reasoning_str}\n" + + if action_detailed_str: + cycle_info_block = f"\n你最近做的事:\n{action_detailed_str}\n" + else: + cycle_info_block = "\n" + # 根据连续文本回复的数量构建提示信息 - # 注意: responses_for_prompt 列表是从最近到最远排序的 if consecutive_text_replies >= 3: # 如果最近的三个活动都是文本回复 cycle_info_block = f'你已经连续回复了三条消息(最近: "{responses_for_prompt[0]}",第二近: "{responses_for_prompt[1]}",第三近: "{responses_for_prompt[2]}")。你回复的有点多了,请注意' elif consecutive_text_replies == 2: # 如果最近的两个活动是文本回复 cycle_info_block = f'你已经连续回复了两条消息(最近: "{responses_for_prompt[0]}",第二近: "{responses_for_prompt[1]}"),请注意' - elif consecutive_text_replies == 1: # 如果最近的一个活动是文本回复 - cycle_info_block = f'你刚刚已经回复一条消息(内容: "{responses_for_prompt[0]}")' # 包装提示块,增加可读性,即使没有连续回复也给个标记 - if cycle_info_block: - cycle_info_block = f"\n你最近的回复\n{cycle_info_block}\n" - else: - cycle_info_block = "\n" + # if cycle_info_block: + # cycle_info_block = f"\n你最近的回复\n{cycle_info_block}\n" + # else: + # cycle_info_block = "\n" # 获取history_loop中最新添加的 if self.history_loop: @@ -70,10 +100,21 @@ class HFCloopObservation: if start_time is not None and end_time is not None: time_diff = int(end_time - start_time) if time_diff > 60: - cycle_info_block += f"\n距离你上一次阅读消息已经过去了{time_diff / 60}分钟\n" + cycle_info_block += f"距离你上一次阅读消息并思考和规划,已经过去了{int(time_diff / 60)}分钟\n" else: - cycle_info_block += f"\n距离你上一次阅读消息已经过去了{time_diff}秒\n" + cycle_info_block += f"距离你上一次阅读消息并思考和规划,已经过去了{time_diff}秒\n" else: - cycle_info_block += "\n你还没看过消息\n" + cycle_info_block += "你还没看过消息\n" self.observe_info = cycle_info_block + + def to_dict(self) -> dict: + """将观察对象转换为可序列化的字典""" + # 只序列化基本信息,避免循环引用 + return { + "observe_info": self.observe_info, + "observe_id": self.observe_id, + "last_observe_time": self.last_observe_time, + # 不序列化history_loop,避免循环引用 + "history_loop_count": len(self.history_loop) + } diff --git a/src/chat/heart_flow/observation/observation.py b/src/chat/heart_flow/observation/observation.py index 97e254fc..5c8b5fda 100644 --- a/src/chat/heart_flow/observation/observation.py +++ b/src/chat/heart_flow/observation/observation.py @@ -13,5 +13,13 @@ class Observation: self.observe_id = observe_id self.last_observe_time = datetime.now().timestamp() # 初始化为当前时间 + def to_dict(self) -> dict: + """将观察对象转换为可序列化的字典""" + return { + "observe_info": self.observe_info, + "observe_id": self.observe_id, + "last_observe_time": self.last_observe_time + } + async def observe(self): pass diff --git a/src/chat/heart_flow/observation/structure_observation.py b/src/chat/heart_flow/observation/structure_observation.py index 73b5bf75..6e670f5e 100644 --- a/src/chat/heart_flow/observation/structure_observation.py +++ b/src/chat/heart_flow/observation/structure_observation.py @@ -15,6 +15,16 @@ class StructureObservation: self.history_loop = [] self.structured_info = [] + def to_dict(self) -> dict: + """将观察对象转换为可序列化的字典""" + return { + "observe_info": self.observe_info, + "observe_id": self.observe_id, + "last_observe_time": self.last_observe_time, + "history_loop": self.history_loop, + "structured_info": self.structured_info + } + def get_observe_info(self): return self.structured_info diff --git a/src/chat/heart_flow/observation/working_observation.py b/src/chat/heart_flow/observation/working_observation.py index 7013c3a2..3cab4a37 100644 --- a/src/chat/heart_flow/observation/working_observation.py +++ b/src/chat/heart_flow/observation/working_observation.py @@ -32,3 +32,13 @@ class WorkingMemoryObservation: async def observe(self): pass + + def to_dict(self) -> dict: + """将观察对象转换为可序列化的字典""" + return { + "observe_info": self.observe_info, + "observe_id": self.observe_id, + "last_observe_time": self.last_observe_time, + "working_memory": self.working_memory.to_dict() if hasattr(self.working_memory, 'to_dict') else str(self.working_memory), + "retrieved_working_memory": [item.to_dict() if hasattr(item, 'to_dict') else str(item) for item in self.retrieved_working_memory] + } diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index 435a7b61..984b3638 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -278,10 +278,6 @@ class SubHeartflow: self.update_last_chat_state_time() self.history_chat_state.append((current_state, self.chat_state_last_time)) - # logger.info( - # f"{log_prefix} 麦麦的聊天状态从 {current_state.value} (持续了 {int(self.chat_state_last_time)} 秒) 变更为 {new_state.value}" - # ) - self.chat_state.chat_status = new_state self.chat_state_last_time = 0 self.chat_state_changed_time = time.time() diff --git a/src/chat/normal_chat/normal_prompt.py b/src/chat/normal_chat/normal_prompt.py index 5b82b8b8..e4d69a0f 100644 --- a/src/chat/normal_chat/normal_prompt.py +++ b/src/chat/normal_chat/normal_prompt.py @@ -185,20 +185,23 @@ class PromptBuilder: # 关键词检测与反应 keywords_reaction_prompt = "" - for rule in global_config.keyword_reaction.rules: - if rule.enable: - if any(keyword in message_txt for keyword in rule.keywords): - logger.info(f"检测到以下关键词之一:{rule.keywords},触发反应:{rule.reaction}") - keywords_reaction_prompt += f"{rule.reaction}," - else: - for pattern in rule.regex: - if result := pattern.search(message_txt): - reaction = rule.reaction - for name, content in result.groupdict().items(): - reaction = reaction.replace(f"[{name}]", content) - logger.info(f"匹配到以下正则表达式:{pattern},触发反应:{reaction}") - keywords_reaction_prompt += reaction + "," - break + try: + for rule in global_config.keyword_reaction.rules: + if rule.enable: + if any(keyword in message_txt for keyword in rule.keywords): + logger.info(f"检测到以下关键词之一:{rule.keywords},触发反应:{rule.reaction}") + keywords_reaction_prompt += f"{rule.reaction}," + else: + for pattern in rule.regex: + if result := pattern.search(message_txt): + reaction = rule.reaction + for name, content in result.groupdict().items(): + reaction = reaction.replace(f"[{name}]", content) + logger.info(f"匹配到以下正则表达式:{pattern},触发反应:{reaction}") + keywords_reaction_prompt += reaction + "," + break + except Exception as e: + logger.warning(f"关键词检测与反应时发生异常,可能是配置文件有误,跳过关键词匹配: {str(e)}") # 中文高手(新加的好玩功能) prompt_ger = "" diff --git a/src/config/official_configs.py b/src/config/official_configs.py index af729db3..1a1469fe 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -143,9 +143,15 @@ class FocusChatConfig(ConfigBase): think_interval: float = 1 """思考间隔(秒)""" - + consecutive_replies: float = 1 """连续回复能力,值越高,麦麦连续回复的概率越高""" + + parallel_processing: bool = False + """是否允许处理器阶段和回忆阶段并行执行""" + + processor_max_time: int = 25 + """处理器最大时间,单位秒,如果超过这个时间,处理器会自动停止""" @dataclass @@ -160,6 +166,11 @@ class FocusChatProcessorConfig(ConfigBase): working_memory_processor: bool = True """是否启用工作记忆处理器""" + + lite_chat_mind_processor: bool = False + """是否启用轻量级聊天思维处理器,可以节省token消耗和时间""" + + @dataclass diff --git a/src/individuality/expression_style.py b/src/individuality/expression_style.py index f4eed60b..cb3778da 100644 --- a/src/individuality/expression_style.py +++ b/src/individuality/expression_style.py @@ -14,13 +14,13 @@ def init_prompt() -> None: personality_expression_prompt = """ {personality} -请从以上人设中总结出这个角色可能的语言风格 +请从以上人设中总结出这个角色可能的语言风格,你必须严格根据人设引申,不要输出例子 思考回复的特殊内容和情感 思考有没有特殊的梗,一并总结成语言风格 总结成如下格式的规律,总结的内容要详细,但具有概括性: 当"xxx"时,可以"xxx", xxx不超过10个字 -例如: +例如(不要输出例子): 当"表示十分惊叹"时,使用"我嘞个xxxx" 当"表示讽刺的赞同,不想讲道理"时,使用"对对对" 当"想说明某个观点,但懒得明说",使用"懂的都懂" @@ -34,13 +34,12 @@ class PersonalityExpression: def __init__(self): self.express_learn_model: LLMRequest = LLMRequest( model=global_config.model.focus_expressor, - temperature=0.1, - max_tokens=256, + max_tokens=512, request_type="expressor.learner", ) self.meta_file_path = os.path.join("data", "expression", "personality", "expression_style_meta.json") self.expressions_file_path = os.path.join("data", "expression", "personality", "expressions.json") - self.max_calculations = 5 + self.max_calculations = 10 def _read_meta_data(self): if os.path.exists(self.meta_file_path): diff --git a/src/llm_models/utils_model.py b/src/llm_models/utils_model.py index 712d51d8..3df45460 100644 --- a/src/llm_models/utils_model.py +++ b/src/llm_models/utils_model.py @@ -753,8 +753,13 @@ class LLMRequest: response = await self._execute_request(endpoint="/chat/completions", payload=data, prompt=prompt) # 原样返回响应,不做处理 - - return response + + if len(response) == 3: + content, reasoning_content, tool_calls = response + return content, (reasoning_content, self.model_name, tool_calls) + else: + content, reasoning_content = response + return content, (reasoning_content, self.model_name) async def generate_response_tool_async(self, prompt: str, tools: list, **kwargs) -> tuple[str, str, list]: """异步方式根据输入的提示生成模型的响应""" diff --git a/src/plugins.md b/src/plugins.md deleted file mode 100644 index 71ca741a..00000000 --- a/src/plugins.md +++ /dev/null @@ -1,101 +0,0 @@ -# 如何编写MaiBot插件 - -## 基本步骤 - -1. 在`src/plugins/你的插件名/actions/`目录下创建插件文件 -2. 继承`PluginAction`基类 -3. 实现`process`方法 - -## 插件结构示例 - -```python -from src.common.logger_manager import get_logger -from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action -from typing import Tuple - -logger = get_logger("your_action_name") - -@register_action -class YourAction(PluginAction): - """你的动作描述""" - - action_name = "your_action_name" # 动作名称,必须唯一 - action_description = "这个动作的详细描述,会展示给用户" - action_parameters = { - "param1": "参数1的说明(可选)", - "param2": "参数2的说明(可选)" - } - action_require = [ - "使用场景1", - "使用场景2" - ] - default = False # 是否默认启用 - - async def process(self) -> Tuple[bool, str]: - """插件核心逻辑""" - # 你的代码逻辑... - return True, "执行结果" -``` - -## 可用的API方法 - -插件可以使用`PluginAction`基类提供的以下API: - -### 1. 发送消息 - -```python -await self.send_message("要发送的文本", target="可选的回复目标") -``` - -### 2. 获取聊天类型 - -```python -chat_type = self.get_chat_type() # 返回 "group" 或 "private" 或 "unknown" -``` - -### 3. 获取最近消息 - -```python -messages = self.get_recent_messages(count=5) # 获取最近5条消息 -# 返回格式: [{"sender": "发送者", "content": "内容", "timestamp": 时间戳}, ...] -``` - -### 4. 获取动作参数 - -```python -param_value = self.action_data.get("param_name", "默认值") -``` - -### 5. 日志记录 - -```python -logger.info(f"{self.log_prefix} 你的日志信息") -logger.warning("警告信息") -logger.error("错误信息") -``` - -## 返回值说明 - -`process`方法必须返回一个元组,包含两个元素: -- 第一个元素(bool): 表示动作是否执行成功 -- 第二个元素(str): 执行结果的文本描述 - -```python -return True, "执行成功的消息" -# 或 -return False, "执行失败的原因" -``` - -## 最佳实践 - -1. 使用`action_parameters`清晰定义你的动作需要的参数 -2. 使用`action_require`描述何时应该使用你的动作 -3. 使用`action_description`准确描述你的动作功能 -4. 使用`logger`记录重要信息,方便调试 -5. 避免操作底层系统,尽量使用`PluginAction`提供的API - -## 注册与加载 - -插件会在系统启动时自动加载,只要放在正确的目录并添加了`@register_action`装饰器。 - -若设置`default = True`,插件会自动添加到默认动作集;否则需要在系统中手动启用。 diff --git a/src/plugins/test_plugin/actions/mute_action.py b/src/plugins/test_plugin/actions/mute_action.py index 21a8e057..5bae71e0 100644 --- a/src/plugins/test_plugin/actions/mute_action.py +++ b/src/plugins/test_plugin/actions/mute_action.py @@ -23,7 +23,7 @@ class MuteAction(PluginAction): "当有人戳你两次以上时,防止刷屏,禁言他,必须牢记", "当你想回避某个话题时使用", ] - default = True # 默认动作,是否手动添加到使用集 + default = False # 默认动作,是否手动添加到使用集 associated_types = ["command", "text"] # associated_types = ["text"] diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 95d329cd..439a6e12 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -41,7 +41,7 @@ identity_detail = [ [expression] # 表达方式 expression_style = "描述麦麦说话的表达风格,表达习惯,例如:(回复尽量简短一些。可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,平淡一些。不要有额外的符号,尽量简单简短)" -enable_expression_learning = true # 是否启用表达学习,麦麦会学习人类说话风格 +enable_expression_learning = false # 是否启用表达学习,麦麦会学习人类说话风格 learning_interval = 600 # 学习间隔 单位秒 [relationship] @@ -94,8 +94,11 @@ talk_frequency_down_groups = [] #降低回复频率的群号码 think_interval = 3 # 思考间隔 单位秒,可以有效减少消耗 consecutive_replies = 1 # 连续回复能力,值越高,麦麦连续回复的概率越高 +parallel_processing = true # 是否并行处理回忆和处理器阶段,可以节省时间 -observation_context_size = 16 # 观察到的最长上下文大小,建议15,太短太长都会导致脑袋尖尖 +processor_max_time = 25 # 处理器最大时间,单位秒,如果超过这个时间,处理器会自动停止 + +observation_context_size = 16 # 观察到的最长上下文大小 compressed_length = 8 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度,超过心流观察到的上下文长度,会压缩,最短压缩长度为5 compress_length_limit = 4 #最多压缩份数,超过该数值的压缩上下文会被删除