From 316415c33b2c2c2c29fe7d65ce0718c7f3457ee7 Mon Sep 17 00:00:00 2001 From: corolin Date: Sat, 22 Mar 2025 16:56:44 +0800 Subject: [PATCH 01/18] fix issue #531 --- .github/workflows/docker-image.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index e88dbf63..c06d967c 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -22,18 +22,18 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v3 with: - username: ${{ vars.DOCKERHUB_USERNAME }} + username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Determine Image Tags id: tags run: | if [[ "${{ github.ref }}" == refs/tags/* ]]; then - echo "tags=${{ vars.DOCKERHUB_USERNAME }}/maimbot:${{ github.ref_name }},${{ vars.DOCKERHUB_USERNAME }}/maimbot:latest" >> $GITHUB_OUTPUT + echo "tags=${{ secrets.DOCKERHUB_USERNAME }}/maimbot:${{ github.ref_name }},${{ secrets.DOCKERHUB_USERNAME }}/maimbot:latest" >> $GITHUB_OUTPUT elif [ "${{ github.ref }}" == "refs/heads/main" ]; then - echo "tags=${{ vars.DOCKERHUB_USERNAME }}/maimbot:main,${{ vars.DOCKERHUB_USERNAME }}/maimbot:latest" >> $GITHUB_OUTPUT + echo "tags=${{ secrets.DOCKERHUB_USERNAME }}/maimbot:main,${{ secrets.DOCKERHUB_USERNAME }}/maimbot:latest" >> $GITHUB_OUTPUT elif [ "${{ github.ref }}" == "refs/heads/main-fix" ]; then - echo "tags=${{ vars.DOCKERHUB_USERNAME }}/maimbot:main-fix" >> $GITHUB_OUTPUT + echo "tags=${{ secrets.DOCKERHUB_USERNAME }}/maimbot:main-fix" >> $GITHUB_OUTPUT fi - name: Build and Push Docker Image @@ -44,5 +44,5 @@ jobs: platforms: linux/amd64,linux/arm64 tags: ${{ steps.tags.outputs.tags }} push: true - cache-from: type=registry,ref=${{ vars.DOCKERHUB_USERNAME }}/maimbot:buildcache - cache-to: type=registry,ref=${{ vars.DOCKERHUB_USERNAME }}/maimbot:buildcache,mode=max + cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maimbot:buildcache + cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maimbot:buildcache,mode=max From 8bad58afe240321d40f6ac88c4402c8a5bc87b85 Mon Sep 17 00:00:00 2001 From: zzzzzyc <104435384+zzzzzyc@users.noreply.github.com> Date: Sat, 22 Mar 2025 22:24:39 +0800 Subject: [PATCH 02/18] Add files via upload --- 配置文件错误排查.py | 617 ++++++++++++++++++++++++++++++++++++ 1 file changed, 617 insertions(+) create mode 100644 配置文件错误排查.py diff --git a/配置文件错误排查.py b/配置文件错误排查.py new file mode 100644 index 00000000..11417113 --- /dev/null +++ b/配置文件错误排查.py @@ -0,0 +1,617 @@ +import tomli +import sys +import re +from pathlib import Path +from typing import Dict, Any, List, Set, Tuple + +def load_toml_file(file_path: str) -> Dict[str, Any]: + """加载TOML文件""" + try: + with open(file_path, "rb") as f: + return tomli.load(f) + except Exception as e: + print(f"错误: 无法加载配置文件 {file_path}: {str(e)} 请检查文件是否存在或者他妈的有没有东西没写值") + sys.exit(1) + +def load_env_file(file_path: str) -> Dict[str, str]: + """加载.env文件中的环境变量""" + env_vars = {} + try: + with open(file_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + if '=' in line: + key, value = line.split('=', 1) + key = key.strip() + value = value.strip() + + # 处理注释 + if '#' in value: + value = value.split('#', 1)[0].strip() + + # 处理引号 + if (value.startswith('"') and value.endswith('"')) or \ + (value.startswith("'") and value.endswith("'")): + value = value[1:-1] + + env_vars[key] = value + return env_vars + except Exception as e: + print(f"警告: 无法加载.env文件 {file_path}: {str(e)}") + return {} + +def check_required_sections(config: Dict[str, Any]) -> List[str]: + """检查必要的配置段是否存在""" + required_sections = [ + "inner", "bot", "personality", "message", "emoji", + "cq_code", "response", "willing", "memory", "mood", + "groups", "model" + ] + missing_sections = [] + + for section in required_sections: + if section not in config: + missing_sections.append(section) + + return missing_sections + +def check_probability_sum(config: Dict[str, Any]) -> List[Tuple[str, float]]: + """检查概率总和是否为1""" + errors = [] + + # 检查人格概率 + if "personality" in config: + personality = config["personality"] + prob_sum = sum([ + personality.get("personality_1_probability", 0), + personality.get("personality_2_probability", 0), + personality.get("personality_3_probability", 0) + ]) + if abs(prob_sum - 1.0) > 0.001: # 允许有小数点精度误差 + errors.append(("人格概率总和", prob_sum)) + + # 检查响应模型概率 + if "response" in config: + response = config["response"] + model_prob_sum = sum([ + response.get("model_r1_probability", 0), + response.get("model_v3_probability", 0), + response.get("model_r1_distill_probability", 0) + ]) + if abs(model_prob_sum - 1.0) > 0.001: + errors.append(("响应模型概率总和", model_prob_sum)) + + return errors + +def check_probability_range(config: Dict[str, Any]) -> List[Tuple[str, float]]: + """检查概率值是否在0-1范围内""" + errors = [] + + # 收集所有概率值 + prob_fields = [] + + # 人格概率 + if "personality" in config: + personality = config["personality"] + prob_fields.extend([ + ("personality.personality_1_probability", personality.get("personality_1_probability")), + ("personality.personality_2_probability", personality.get("personality_2_probability")), + ("personality.personality_3_probability", personality.get("personality_3_probability")) + ]) + + # 消息概率 + if "message" in config: + message = config["message"] + prob_fields.append(("message.emoji_chance", message.get("emoji_chance"))) + + # 响应模型概率 + if "response" in config: + response = config["response"] + prob_fields.extend([ + ("response.model_r1_probability", response.get("model_r1_probability")), + ("response.model_v3_probability", response.get("model_v3_probability")), + ("response.model_r1_distill_probability", response.get("model_r1_distill_probability")) + ]) + + # 情绪衰减率 + if "mood" in config: + mood = config["mood"] + prob_fields.append(("mood.mood_decay_rate", mood.get("mood_decay_rate"))) + + # 中文错别字概率 + if "chinese_typo" in config and config["chinese_typo"].get("enable", False): + typo = config["chinese_typo"] + prob_fields.extend([ + ("chinese_typo.error_rate", typo.get("error_rate")), + ("chinese_typo.tone_error_rate", typo.get("tone_error_rate")), + ("chinese_typo.word_replace_rate", typo.get("word_replace_rate")) + ]) + + # 检查所有概率值是否在0-1范围内 + for field_name, value in prob_fields: + if value is not None and (value < 0 or value > 1): + errors.append((field_name, value)) + + return errors + +def check_model_configurations(config: Dict[str, Any], env_vars: Dict[str, str]) -> List[str]: + """检查模型配置是否完整,并验证provider是否正确""" + errors = [] + + if "model" not in config: + return ["缺少[model]部分"] + + required_models = [ + "llm_reasoning", "llm_reasoning_minor", "llm_normal", + "llm_normal_minor", "llm_emotion_judge", "llm_topic_judge", + "llm_summary_by_topic", "vlm", "embedding" + ] + + # 从环境变量中提取有效的API提供商 + valid_providers = set() + for key in env_vars: + if key.endswith('_BASE_URL'): + provider_name = key.replace('_BASE_URL', '') + valid_providers.add(provider_name) + + # 将provider名称标准化以便比较 + provider_mapping = { + "SILICONFLOW": ["SILICONFLOW", "SILICON_FLOW", "SILICON-FLOW"], + "CHAT_ANY_WHERE": ["CHAT_ANY_WHERE", "CHAT-ANY-WHERE", "CHATANYWHERE"], + "DEEP_SEEK": ["DEEP_SEEK", "DEEP-SEEK", "DEEPSEEK"] + } + + # 创建反向映射表,用于检查错误拼写 + reverse_mapping = {} + for standard, variants in provider_mapping.items(): + for variant in variants: + reverse_mapping[variant.upper()] = standard + + for model_name in required_models: + # 检查model下是否有对应子部分 + if model_name not in config["model"]: + errors.append(f"缺少[model.{model_name}]配置") + else: + model_config = config["model"][model_name] + if "name" not in model_config: + errors.append(f"[model.{model_name}]缺少name属性") + + if "provider" not in model_config: + errors.append(f"[model.{model_name}]缺少provider属性") + else: + provider = model_config["provider"].upper() + + # 检查拼写错误 + for known_provider, correct_provider in reverse_mapping.items(): + # 使用模糊匹配检测拼写错误 + if provider != known_provider and _similar_strings(provider, known_provider) and provider not in reverse_mapping: + errors.append(f"[model.{model_name}]的provider '{model_config['provider']}' 可能拼写错误,应为 '{known_provider}'") + break + + return errors + +def _similar_strings(s1: str, s2: str) -> bool: + """简单检查两个字符串是否相似(用于检测拼写错误)""" + # 如果两个字符串长度相差过大,则认为不相似 + if abs(len(s1) - len(s2)) > 2: + return False + + # 计算相同字符的数量 + common_chars = sum(1 for c1, c2 in zip(s1, s2) if c1 == c2) + # 如果相同字符比例超过80%,则认为相似 + return common_chars / max(len(s1), len(s2)) > 0.8 + +def check_api_providers(config: Dict[str, Any], env_vars: Dict[str, str]) -> List[str]: + """检查配置文件中的API提供商是否与环境变量中的一致""" + errors = [] + + if "model" not in config: + return ["缺少[model]部分"] + + # 从环境变量中提取有效的API提供商 + valid_providers = {} + for key in env_vars: + if key.endswith('_BASE_URL'): + provider_name = key.replace('_BASE_URL', '') + base_url = env_vars[key] + valid_providers[provider_name] = { + "base_url": base_url, + "key": env_vars.get(f"{provider_name}_KEY", "") + } + + # 检查配置文件中使用的所有提供商 + used_providers = set() + for model_category, model_config in config["model"].items(): + if "provider" in model_config: + provider = model_config["provider"] + used_providers.add(provider) + + # 检查此提供商是否在环境变量中定义 + normalized_provider = provider.replace(" ", "_").upper() + found = False + for env_provider in valid_providers: + if normalized_provider == env_provider: + found = True + break + # 尝试更宽松的匹配(例如SILICONFLOW可能匹配SILICON_FLOW) + elif normalized_provider.replace("_", "") == env_provider.replace("_", ""): + found = True + errors.append(f"提供商 '{provider}' 在环境变量中的名称是 '{env_provider}', 建议统一命名") + break + + if not found: + errors.append(f"提供商 '{provider}' 在环境变量中未定义") + + # 特别检查常见的拼写错误 + for provider in used_providers: + if provider.upper() == "SILICONFOLW": + errors.append(f"提供商 'SILICONFOLW' 存在拼写错误,应为 'SILICONFLOW'") + + return errors + +def check_groups_configuration(config: Dict[str, Any]) -> List[str]: + """检查群组配置""" + errors = [] + + if "groups" not in config: + return ["缺少[groups]部分"] + + groups = config["groups"] + + # 检查talk_allowed是否为列表 + if "talk_allowed" not in groups: + errors.append("缺少groups.talk_allowed配置") + elif not isinstance(groups["talk_allowed"], list): + errors.append("groups.talk_allowed应该是一个列表") + else: + # 检查talk_allowed是否包含默认示例值123 + if 123 in groups["talk_allowed"]: + errors.append({ + "main": "groups.talk_allowed中存在默认示例值'123',请修改为真实的群号", + "details": [ + f" 当前值: {groups['talk_allowed']}", + f" '123'为示例值,需要替换为真实群号" + ] + }) + + # 检查是否存在重复的群号 + talk_allowed = groups["talk_allowed"] + duplicates = [] + seen = set() + for gid in talk_allowed: + if gid in seen and gid not in duplicates: + duplicates.append(gid) + seen.add(gid) + + if duplicates: + errors.append({ + "main": "groups.talk_allowed中存在重复的群号", + "details": [f" 重复的群号: {duplicates}"] + }) + + # 检查其他群组配置 + if "talk_frequency_down" in groups and not isinstance(groups["talk_frequency_down"], list): + errors.append("groups.talk_frequency_down应该是一个列表") + + if "ban_user_id" in groups and not isinstance(groups["ban_user_id"], list): + errors.append("groups.ban_user_id应该是一个列表") + + return errors + +def check_keywords_reaction(config: Dict[str, Any]) -> List[str]: + """检查关键词反应配置""" + errors = [] + + if "keywords_reaction" not in config: + return ["缺少[keywords_reaction]部分"] + + kr = config["keywords_reaction"] + + # 检查enable字段 + if "enable" not in kr: + errors.append("缺少keywords_reaction.enable配置") + + # 检查规则配置 + if "rules" not in kr: + errors.append("缺少keywords_reaction.rules配置") + elif not isinstance(kr["rules"], list): + errors.append("keywords_reaction.rules应该是一个列表") + else: + for i, rule in enumerate(kr["rules"]): + if "enable" not in rule: + errors.append(f"关键词规则 #{i+1} 缺少enable字段") + if "keywords" not in rule: + errors.append(f"关键词规则 #{i+1} 缺少keywords字段") + elif not isinstance(rule["keywords"], list): + errors.append(f"关键词规则 #{i+1} 的keywords应该是一个列表") + if "reaction" not in rule: + errors.append(f"关键词规则 #{i+1} 缺少reaction字段") + + return errors + +def check_willing_mode(config: Dict[str, Any]) -> List[str]: + """检查回复意愿模式配置""" + errors = [] + + if "willing" not in config: + return ["缺少[willing]部分"] + + willing = config["willing"] + + if "willing_mode" not in willing: + errors.append("缺少willing.willing_mode配置") + elif willing["willing_mode"] not in ["classical", "dynamic", "custom"]: + errors.append(f"willing.willing_mode值无效: {willing['willing_mode']}, 应为classical/dynamic/custom") + + return errors + +def check_memory_config(config: Dict[str, Any]) -> List[str]: + """检查记忆系统配置""" + errors = [] + + if "memory" not in config: + return ["缺少[memory]部分"] + + memory = config["memory"] + + # 检查必要的参数 + required_fields = [ + "build_memory_interval", "memory_compress_rate", + "forget_memory_interval", "memory_forget_time", + "memory_forget_percentage" + ] + + for field in required_fields: + if field not in memory: + errors.append(f"缺少memory.{field}配置") + + # 检查参数值的有效性 + if "memory_compress_rate" in memory and (memory["memory_compress_rate"] <= 0 or memory["memory_compress_rate"] > 1): + errors.append(f"memory.memory_compress_rate值无效: {memory['memory_compress_rate']}, 应在0-1之间") + + if "memory_forget_percentage" in memory and (memory["memory_forget_percentage"] <= 0 or memory["memory_forget_percentage"] > 1): + errors.append(f"memory.memory_forget_percentage值无效: {memory['memory_forget_percentage']}, 应在0-1之间") + + return errors + +def check_personality_config(config: Dict[str, Any]) -> List[str]: + """检查人格配置""" + errors = [] + + if "personality" not in config: + return ["缺少[personality]部分"] + + personality = config["personality"] + + # 检查prompt_personality是否存在且为数组 + if "prompt_personality" not in personality: + errors.append("缺少personality.prompt_personality配置") + elif not isinstance(personality["prompt_personality"], list): + errors.append("personality.prompt_personality应该是一个数组") + else: + # 检查数组长度 + if len(personality["prompt_personality"]) < 1: + errors.append(f"personality.prompt_personality数组长度不足,当前长度: {len(personality['prompt_personality'])}, 需要至少1项") + else: + # 模板默认值 + template_values = [ + "用一句话或几句话描述性格特点和其他特征", + "用一句话或几句话描述性格特点和其他特征", + "例如,是一个热爱国家热爱党的新时代好青年" + ] + + # 检查是否仍然使用默认模板值 + error_details = [] + for i, (current, template) in enumerate(zip(personality["prompt_personality"][:3], template_values)): + if current == template: + error_details.append({ + "main": f"personality.prompt_personality第{i+1}项仍使用默认模板值,请自定义", + "details": [ + f" 当前值: '{current}'", + f" 请不要使用模板值: '{template}'" + ] + }) + + # 将错误添加到errors列表 + for error in error_details: + errors.append(error) + + return errors + +def check_bot_config(config: Dict[str, Any]) -> List[str]: + """检查机器人基础配置""" + errors = [] + infos = [] + + if "bot" not in config: + return ["缺少[bot]部分"] + + bot = config["bot"] + + # 检查QQ号是否为默认值或测试值 + if "qq" not in bot: + errors.append("缺少bot.qq配置") + elif bot["qq"] == 1 or bot["qq"] == 123: + errors.append(f"QQ号 '{bot['qq']}' 似乎是默认值或测试值,请设置为真实的QQ号") + else: + infos.append(f"当前QQ号: {bot['qq']}") + + # 检查昵称是否设置 + if "nickname" not in bot or not bot["nickname"]: + errors.append("缺少bot.nickname配置或昵称为空") + elif bot["nickname"]: + infos.append(f"当前昵称: {bot['nickname']}") + + # 检查别名是否为列表 + if "alias_names" in bot and not isinstance(bot["alias_names"], list): + errors.append("bot.alias_names应该是一个列表") + + return errors, infos + +def format_results(all_errors): + """格式化检查结果""" + sections_errors, prob_sum_errors, prob_range_errors, model_errors, api_errors, groups_errors, kr_errors, willing_errors, memory_errors, personality_errors, bot_results = all_errors + bot_errors, bot_infos = bot_results + + if not any([sections_errors, prob_sum_errors, prob_range_errors, model_errors, api_errors, groups_errors, kr_errors, willing_errors, memory_errors, personality_errors, bot_errors]): + result = "✅ 配置文件检查通过,未发现问题。" + + # 添加机器人信息 + if bot_infos: + result += "\n\n【机器人信息】" + for info in bot_infos: + result += f"\n - {info}" + + return result + + output = [] + output.append("❌ 配置文件检查发现以下问题:") + + if sections_errors: + output.append("\n【缺失的配置段】") + for section in sections_errors: + output.append(f" - {section}") + + if prob_sum_errors: + output.append("\n【概率总和错误】(应为1.0)") + for name, value in prob_sum_errors: + output.append(f" - {name}: {value:.4f}") + + if prob_range_errors: + output.append("\n【概率值范围错误】(应在0-1之间)") + for name, value in prob_range_errors: + output.append(f" - {name}: {value}") + + if model_errors: + output.append("\n【模型配置错误】") + for error in model_errors: + output.append(f" - {error}") + + if api_errors: + output.append("\n【API提供商错误】") + for error in api_errors: + output.append(f" - {error}") + + if groups_errors: + output.append("\n【群组配置错误】") + for error in groups_errors: + if isinstance(error, dict): + output.append(f" - {error['main']}") + for detail in error['details']: + output.append(f"{detail}") + else: + output.append(f" - {error}") + + if kr_errors: + output.append("\n【关键词反应配置错误】") + for error in kr_errors: + output.append(f" - {error}") + + if willing_errors: + output.append("\n【回复意愿配置错误】") + for error in willing_errors: + output.append(f" - {error}") + + if memory_errors: + output.append("\n【记忆系统配置错误】") + for error in memory_errors: + output.append(f" - {error}") + + if personality_errors: + output.append("\n【人格配置错误】") + for error in personality_errors: + if isinstance(error, dict): + output.append(f" - {error['main']}") + for detail in error['details']: + output.append(f"{detail}") + else: + output.append(f" - {error}") + + if bot_errors: + output.append("\n【机器人基础配置错误】") + for error in bot_errors: + output.append(f" - {error}") + + # 添加机器人信息,即使有错误 + if bot_infos: + output.append("\n【机器人信息】") + for info in bot_infos: + output.append(f" - {info}") + + return "\n".join(output) + +def main(): + # 获取配置文件路径 + config_path = Path("config/bot_config.toml") + env_path = Path(".env.prod") + + if not config_path.exists(): + print(f"错误: 找不到配置文件 {config_path}") + return + + if not env_path.exists(): + print(f"警告: 找不到环境变量文件 {env_path}, 将跳过API提供商检查") + env_vars = {} + else: + env_vars = load_env_file(env_path) + + # 加载配置文件 + config = load_toml_file(config_path) + + # 运行各种检查 + sections_errors = check_required_sections(config) + prob_sum_errors = check_probability_sum(config) + prob_range_errors = check_probability_range(config) + model_errors = check_model_configurations(config, env_vars) + api_errors = check_api_providers(config, env_vars) + groups_errors = check_groups_configuration(config) + kr_errors = check_keywords_reaction(config) + willing_errors = check_willing_mode(config) + memory_errors = check_memory_config(config) + personality_errors = check_personality_config(config) + bot_results = check_bot_config(config) + + # 格式化并打印结果 + all_errors = (sections_errors, prob_sum_errors, prob_range_errors, model_errors, api_errors, groups_errors, kr_errors, willing_errors, memory_errors, personality_errors, bot_results) + result = format_results(all_errors) + print("📋 机器人配置检查结果:") + print(result) + + # 综合评估 + total_errors = 0 + + # 解包bot_results + bot_errors, _ = bot_results + + # 计算普通错误列表的长度 + for errors in [sections_errors, model_errors, api_errors, groups_errors, kr_errors, willing_errors, memory_errors, bot_errors]: + total_errors += len(errors) + + # 计算元组列表的长度(概率相关错误) + total_errors += len(prob_sum_errors) + total_errors += len(prob_range_errors) + + # 特殊处理personality_errors和groups_errors + for errors_list in [personality_errors, groups_errors]: + for error in errors_list: + if isinstance(error, dict): + # 每个字典表示一个错误,而不是每行都算一个 + total_errors += 1 + else: + total_errors += 1 + + if total_errors > 0: + print(f"\n总计发现 {total_errors} 个配置问题。") + print("\n建议:") + print("1. 修复所有错误后再运行机器人") + print("2. 特别注意拼写错误,例如不!要!写!错!别!字!!!!!") + print("3. 确保所有API提供商名称与环境变量中一致") + print("4. 检查概率值设置,确保总和为1") + else: + print("\n您的配置文件完全正确!机器人可以正常运行。") + +if __name__ == "__main__": + main() + input("\n按任意键退出...") \ No newline at end of file From ddd8ca321396e438a85b07f8fd845b8af255513e Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 25 Mar 2025 15:53:43 +0800 Subject: [PATCH 03/18] Update current_mind.py --- src/think_flow_demo/current_mind.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/think_flow_demo/current_mind.py b/src/think_flow_demo/current_mind.py index fd4ca616..09634cf2 100644 --- a/src/think_flow_demo/current_mind.py +++ b/src/think_flow_demo/current_mind.py @@ -40,7 +40,7 @@ class SubHeartflow: await self.do_a_thinking() print("麦麦闹情绪了") await self.judge_willing() - await asyncio.sleep(20) + await asyncio.sleep(30) async def do_a_thinking(self): print("麦麦小脑袋转起来了") @@ -109,7 +109,7 @@ class SubHeartflow: prompt += f"你现在的想法是{current_thinking_info}。" prompt += f"你现在{mood_info}。" prompt += f"现在请你思考,你想不想发言或者回复,请你输出一个数字,1-10,1表示非常不想,10表示非常想。" - prompt += f"请你用<>包裹你的回复意愿,例如输出<1>表示不想回复,输出<10>表示非常想回复。<5>表示想回复,但是需要思考一下。" + prompt += f"请你用<>包裹你的回复意愿,例如输出<1>表示不想回复,输出<10>表示非常想回复。请你考虑,你完全可以不回复" response, reasoning_content = await self.llm_model.generate_response_async(prompt) # 解析willing值 From 4e7efb4271a8b3eeaa554c2cf46a391749b1ade0 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 25 Mar 2025 17:03:39 +0800 Subject: [PATCH 04/18] =?UTF-8?q?fix=20=E7=A7=BB=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/willing/mode_custom.py | 106 ------------------------- src/think_flow_demo/offline_llm.py | 123 ----------------------------- 2 files changed, 229 deletions(-) delete mode 100644 src/think_flow_demo/offline_llm.py diff --git a/src/plugins/willing/mode_custom.py b/src/plugins/willing/mode_custom.py index a4d647ae..e69de29b 100644 --- a/src/plugins/willing/mode_custom.py +++ b/src/plugins/willing/mode_custom.py @@ -1,106 +0,0 @@ -import asyncio -from typing import Dict -from ..chat.chat_stream import ChatStream - - -class WillingManager: - def __init__(self): - self.chat_reply_willing: Dict[str, float] = {} # 存储每个聊天流的回复意愿 - self._decay_task = None - self._started = False - - async def _decay_reply_willing(self): - """定期衰减回复意愿""" - while True: - await asyncio.sleep(3) - for chat_id in self.chat_reply_willing: - # 每分钟衰减10%的回复意愿 - self.chat_reply_willing[chat_id] = max(0, self.chat_reply_willing[chat_id] * 0.6) - - def get_willing(self, chat_stream: ChatStream) -> float: - """获取指定聊天流的回复意愿""" - if chat_stream: - return self.chat_reply_willing.get(chat_stream.stream_id, 0) - return 0 - - def set_willing(self, chat_id: str, willing: float): - """设置指定聊天流的回复意愿""" - self.chat_reply_willing[chat_id] = willing - - async def change_reply_willing_received( - self, - chat_stream: ChatStream, - topic: str = None, - is_mentioned_bot: bool = False, - config=None, - is_emoji: bool = False, - interested_rate: float = 0, - sender_id: str = None, - ) -> float: - """改变指定聊天流的回复意愿并返回回复概率""" - chat_id = chat_stream.stream_id - current_willing = self.chat_reply_willing.get(chat_id, 0) - - if topic and current_willing < 1: - current_willing += 0.2 - elif topic: - current_willing += 0.05 - - if is_mentioned_bot and current_willing < 1.0: - current_willing += 0.9 - elif is_mentioned_bot: - current_willing += 0.05 - - if is_emoji: - current_willing *= 0.2 - - self.chat_reply_willing[chat_id] = min(current_willing, 3.0) - - reply_probability = (current_willing - 0.5) * 2 - - # 检查群组权限(如果是群聊) - if chat_stream.group_info and config: - if chat_stream.group_info.group_id not in config.talk_allowed_groups: - current_willing = 0 - reply_probability = 0 - - if chat_stream.group_info.group_id in config.talk_frequency_down_groups: - reply_probability = reply_probability / config.down_frequency_rate - - if is_mentioned_bot and sender_id == "1026294844": - reply_probability = 1 - - return reply_probability - - def change_reply_willing_sent(self, chat_stream: ChatStream): - """发送消息后降低聊天流的回复意愿""" - if chat_stream: - chat_id = chat_stream.stream_id - current_willing = self.chat_reply_willing.get(chat_id, 0) - self.chat_reply_willing[chat_id] = max(0, current_willing - 1.8) - - def change_reply_willing_not_sent(self, chat_stream: ChatStream): - """未发送消息后降低聊天流的回复意愿""" - if chat_stream: - chat_id = chat_stream.stream_id - current_willing = self.chat_reply_willing.get(chat_id, 0) - self.chat_reply_willing[chat_id] = max(0, current_willing - 0) - - def change_reply_willing_after_sent(self, chat_stream: ChatStream): - """发送消息后提高聊天流的回复意愿""" - if chat_stream: - chat_id = chat_stream.stream_id - current_willing = self.chat_reply_willing.get(chat_id, 0) - if current_willing < 1: - self.chat_reply_willing[chat_id] = min(1, current_willing + 0.4) - - async def ensure_started(self): - """确保衰减任务已启动""" - if not self._started: - if self._decay_task is None: - self._decay_task = asyncio.create_task(self._decay_reply_willing()) - self._started = True - - -# 创建全局实例 -willing_manager = WillingManager() diff --git a/src/think_flow_demo/offline_llm.py b/src/think_flow_demo/offline_llm.py deleted file mode 100644 index db51ca00..00000000 --- a/src/think_flow_demo/offline_llm.py +++ /dev/null @@ -1,123 +0,0 @@ -import asyncio -import os -import time -from typing import Tuple, Union - -import aiohttp -import requests -from src.common.logger import get_module_logger - -logger = get_module_logger("offline_llm") - - -class LLMModel: - def __init__(self, model_name="Pro/deepseek-ai/DeepSeek-V3", **kwargs): - self.model_name = model_name - self.params = kwargs - self.api_key = os.getenv("SILICONFLOW_KEY") - self.base_url = os.getenv("SILICONFLOW_BASE_URL") - - if not self.api_key or not self.base_url: - raise ValueError("环境变量未正确加载:SILICONFLOW_KEY 或 SILICONFLOW_BASE_URL 未设置") - - logger.info(f"API URL: {self.base_url}") # 使用 logger 记录 base_url - - def generate_response(self, prompt: str) -> Union[str, Tuple[str, str]]: - """根据输入的提示生成模型的响应""" - headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"} - - # 构建请求体 - data = { - "model": self.model_name, - "messages": [{"role": "user", "content": prompt}], - "temperature": 0.5, - **self.params, - } - - # 发送请求到完整的 chat/completions 端点 - api_url = f"{self.base_url.rstrip('/')}/chat/completions" - logger.info(f"Request URL: {api_url}") # 记录请求的 URL - - max_retries = 3 - base_wait_time = 15 # 基础等待时间(秒) - - for retry in range(max_retries): - try: - response = requests.post(api_url, headers=headers, json=data) - - if response.status_code == 429: - wait_time = base_wait_time * (2**retry) # 指数退避 - logger.warning(f"遇到请求限制(429),等待{wait_time}秒后重试...") - time.sleep(wait_time) - continue - - response.raise_for_status() # 检查其他响应状态 - - result = response.json() - if "choices" in result and len(result["choices"]) > 0: - content = result["choices"][0]["message"]["content"] - reasoning_content = result["choices"][0]["message"].get("reasoning_content", "") - return content, reasoning_content - return "没有返回结果", "" - - except Exception as e: - if retry < max_retries - 1: # 如果还有重试机会 - wait_time = base_wait_time * (2**retry) - logger.error(f"[回复]请求失败,等待{wait_time}秒后重试... 错误: {str(e)}") - time.sleep(wait_time) - else: - logger.error(f"请求失败: {str(e)}") - return f"请求失败: {str(e)}", "" - - logger.error("达到最大重试次数,请求仍然失败") - return "达到最大重试次数,请求仍然失败", "" - - async def generate_response_async(self, prompt: str) -> Union[str, Tuple[str, str]]: - """异步方式根据输入的提示生成模型的响应""" - headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"} - - # 构建请求体 - data = { - "model": self.model_name, - "messages": [{"role": "user", "content": prompt}], - "temperature": 0.5, - **self.params, - } - - # 发送请求到完整的 chat/completions 端点 - api_url = f"{self.base_url.rstrip('/')}/chat/completions" - logger.info(f"Request URL: {api_url}") # 记录请求的 URL - - max_retries = 3 - base_wait_time = 15 - - async with aiohttp.ClientSession() as session: - for retry in range(max_retries): - try: - async with session.post(api_url, headers=headers, json=data) as response: - if response.status == 429: - wait_time = base_wait_time * (2**retry) # 指数退避 - logger.warning(f"遇到请求限制(429),等待{wait_time}秒后重试...") - await asyncio.sleep(wait_time) - continue - - response.raise_for_status() # 检查其他响应状态 - - result = await response.json() - if "choices" in result and len(result["choices"]) > 0: - content = result["choices"][0]["message"]["content"] - reasoning_content = result["choices"][0]["message"].get("reasoning_content", "") - return content, reasoning_content - return "没有返回结果", "" - - except Exception as e: - if retry < max_retries - 1: # 如果还有重试机会 - wait_time = base_wait_time * (2**retry) - logger.error(f"[回复]请求失败,等待{wait_time}秒后重试... 错误: {str(e)}") - await asyncio.sleep(wait_time) - else: - logger.error(f"请求失败: {str(e)}") - return f"请求失败: {str(e)}", "" - - logger.error("达到最大重试次数,请求仍然失败") - return "达到最大重试次数,请求仍然失败", "" From c220f4c79e45265ce83ccd0e678d8bd232c7168d Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 25 Mar 2025 21:59:15 +0800 Subject: [PATCH 05/18] =?UTF-8?q?better=20=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=95=B4=E7=90=86,=E9=98=B2=E6=AD=A2=E7=9C=BC?= =?UTF-8?q?=E8=8A=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 123 --- config/bot_config_test.toml | 179 +++++++++++++++++++++++++ src/plugins/chat/__init__.py | 4 +- src/plugins/chat/bot.py | 3 - src/plugins/chat/config.py | 208 +++++++++++++++-------------- src/plugins/chat/emoji_manager.py | 8 +- src/plugins/willing/mode_custom.py | 102 ++++++++++++++ template/bot_config_template.toml | 90 +++++++------ 7 files changed, 446 insertions(+), 148 deletions(-) create mode 100644 config/bot_config_test.toml diff --git a/config/bot_config_test.toml b/config/bot_config_test.toml new file mode 100644 index 00000000..dd01bfde --- /dev/null +++ b/config/bot_config_test.toml @@ -0,0 +1,179 @@ +[inner] +version = "0.0.10" + +[mai_version] +version = "0.6.0" +version-fix = "snapshot-1" + +#以下是给开发人员阅读的,一般用户不需要阅读 +#如果你想要修改配置文件,请在修改后将version的值进行变更 +#如果新增项目,请在BotConfig类下新增相应的变量 +#1.如果你修改的是[]层级项目,例如你新增了 [memory],那么请在config.py的 load_config函数中的include_configs字典中新增"内容":{ +#"func":memory, +#"support":">=0.0.0", #新的版本号 +#"necessary":False #是否必须 +#} +#2.如果你修改的是[]下的项目,例如你新增了[memory]下的 memory_ban_words ,那么请在config.py的 load_config函数中的 memory函数下新增版本判断: + # if config.INNER_VERSION in SpecifierSet(">=0.0.2"): + # config.memory_ban_words = set(memory_config.get("memory_ban_words", [])) + +[bot] +qq = 2814567326 +nickname = "麦麦" +alias_names = ['牢麦', '麦叠', '哈基麦'] + +[personality] +prompt_personality = ['曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧', '是一个女大学生,你有黑色头发,你会刷小红书', '是一个女大学生,你会刷b站,对ACG文化感兴趣'] +personality_1_probability = 0.7 # 第一种人格出现概率 +personality_2_probability = 0.1 # 第二种人格出现概率 +personality_3_probability = 0.2 # 第三种人格出现概率,请确保三个概率相加等于1 +prompt_schedule = "一个曾经学习地质,现在学习心理学和脑科学的女大学生,喜欢刷qq,贴吧,知乎和小红书" + +[message] +min_text_length = 2 # 与麦麦聊天时麦麦只会回答文本大于等于此数的消息 +max_context_size = 10 # 麦麦获得的上文数量 +emoji_chance = 0.2 # 麦麦使用表情包的概率 +thinking_timeout = 100 # 麦麦思考时间 + +response_willing_amplifier = 1 # 麦麦回复意愿放大系数,一般为1 +response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数,听到记忆里的内容时放大系数 +down_frequency_rate = 2 # 降低回复频率的群组回复意愿降低系数 +ban_words = [] + +ban_msgs_regex = [] + +[emoji] +check_interval = 120 # 检查表情包的时间间隔 +register_interval = 10 # 注册表情包的时间间隔 +auto_save = true # 自动偷表情包 +enable_check = false # 是否启用表情包过滤 +check_prompt = "符合公序良俗" # 表情包过滤要求 + +[cq_code] +enable_pic_translate = false + +[response] +model_r1_probability = 0.5 # 麦麦回答时选择主要回复模型1 模型的概率 +model_v3_probability = 0.5 # 麦麦回答时选择次要回复模型2 模型的概率 +model_r1_distill_probability = 0 # 麦麦回答时选择次要回复模型3 模型的概率 +max_response_length = 1024 # 麦麦回答的最大token数 + +[willing] +willing_mode = "classical" # 回复意愿模式 经典模式 +# willing_mode = "dynamic" # 动态模式(可能不兼容) +# willing_mode = "custom" # 自定义模式(可自行调整 + +[memory] +build_memory_interval = 3000 # 记忆构建间隔 单位秒 间隔越低,麦麦学习越多,但是冗余信息也会增多 +build_memory_distribution = [4, 4, 0.6, 48, 36, 0.4] # 记忆构建分布,参数:分布1均值,标准差,权重,分布2均值,标准差,权重 +build_memory_sample_num = 10 # 采样数量,数值越高记忆采样次数越多 +build_memory_sample_length = 30 # 采样长度,数值越高一段记忆内容越丰富 +memory_compress_rate = 0.1 # 记忆压缩率 控制记忆精简程度 建议保持默认,调高可以获得更多信息,但是冗余信息也会增多 + +forget_memory_interval = 300 # 记忆遗忘间隔 单位秒 间隔越低,麦麦遗忘越频繁,记忆更精简,但更难学习 +memory_forget_time = 24 #多长时间后的记忆会被遗忘 单位小时 +memory_forget_percentage = 0.005 # 记忆遗忘比例 控制记忆遗忘程度 越大遗忘越多 建议保持默认 + + +memory_ban_words = ['表情包', '图片', '回复', '聊天记录'] + +[mood] +mood_update_interval = 1.0 # 情绪更新间隔 单位秒 +mood_decay_rate = 0.95 # 情绪衰减率 +mood_intensity_factor = 1.0 # 情绪强度因子 + +[keywords_reaction] # 针对某个关键词作出反应 +enable = true # 关键词反应功能的总开关 + +[[keywords_reaction.rules]] +enable = true +keywords = [ "人机", "bot", "机器", "入机", "robot", "机器人",] +reaction = "有人说你是人机或者机器人,否定这一事实,攻击或反问他。注意,只有认为你是机器人才回复,否则不要否认" + +[[keywords_reaction.rules]] +enable = false +keywords = [ "测试关键词回复", "test", "",] +reaction = "回答“测试成功”" + +[chinese_typo] +enable = true # 是否启用中文错别字生成器 +error_rate=0.01 # 单字替换概率 +min_freq=7 # 最小字频阈值 +tone_error_rate=0.3 # 声调错误概率 +word_replace_rate=0.01 # 整词替换概率 + +[others] +enable_kuuki_read = true # 是否启用读空气功能 +enable_friend_chat = true # 是否启用好友聊天 + +[groups] +talk_allowed = [571780722,1022489779,534940728, 192194125, 851345375, 739044565, 766798517, 1030993430, 435591861, 708847644, 591693379, 571780722, 1028699246, 571780722, 1015816696] #可以回复消息的群 +talk_frequency_down = [1022489779, 571780722] #降低回复频率的群 +ban_user_id = [3488737411, 2732836727, 3878664193, 3799953254] #禁止回复和读取消息的QQ号 + +[remote] #发送统计信息,主要是看全球有多少只麦麦 +enable = true + +#下面的模型若使用硅基流动则不需要更改,使用ds官方则改成.env.prod自定义的宏,使用自定义模型则选择定位相似的模型自己填写 + +#推理模型 + +[model.llm_reasoning] #回复模型1 主要回复模型 +# name = "Pro/deepseek-ai/DeepSeek-R1" +name = "Qwen/QwQ-32B" +provider = "SILICONFLOW" +pri_in = 1.0 #模型的输入价格(非必填,可以记录消耗) +pri_out = 4.0 #模型的输出价格(非必填,可以记录消耗) + +[model.llm_reasoning_minor] #回复模型3 次要回复模型 +name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B" +provider = "SILICONFLOW" +pri_in = 1.26 #模型的输入价格(非必填,可以记录消耗) +pri_out = 1.26 #模型的输出价格(非必填,可以记录消耗) + +#非推理模型 + +[model.llm_normal] #V3 回复模型2 次要回复模型 +name = "Qwen/Qwen2.5-32B-Instruct" +provider = "SILICONFLOW" +pri_in = 1.26 #模型的输入价格(非必填,可以记录消耗) +pri_out = 1.26 #模型的输出价格(非必填,可以记录消耗) + +[model.llm_emotion_judge] #表情包判断 +name = "Qwen/Qwen2.5-14B-Instruct" +provider = "SILICONFLOW" +pri_in = 0.7 +pri_out = 0.7 + +[model.llm_topic_judge] #记忆主题判断:建议使用qwen2.5 7b +name = "Pro/Qwen/Qwen2.5-7B-Instruct" +# name = "Qwen/Qwen2-1.5B-Instruct" +provider = "SILICONFLOW" +pri_in = 0.35 +pri_out = 0.35 + +[model.llm_summary_by_topic] #概括模型,建议使用qwen2.5 32b 及以上 +name = "Qwen/Qwen2.5-32B-Instruct" +provider = "SILICONFLOW" +pri_in = 1.26 +pri_out = 1.26 + +[model.moderation] #内容审核,开发中 +name = "" +provider = "SILICONFLOW" +pri_in = 1.0 +pri_out = 2.0 + +# 识图模型 + +[model.vlm] #图像识别 +name = "Pro/Qwen/Qwen2.5-VL-7B-Instruct" +provider = "SILICONFLOW" +pri_in = 0.35 +pri_out = 0.35 + +#嵌入模型 + +[model.embedding] #嵌入 +name = "BAAI/bge-m3" +provider = "SILICONFLOW" diff --git a/src/plugins/chat/__init__.py b/src/plugins/chat/__init__.py index c4c85bcd..713f1d37 100644 --- a/src/plugins/chat/__init__.py +++ b/src/plugins/chat/__init__.py @@ -77,7 +77,7 @@ async def start_background_tasks(): logger.success("心流系统启动成功") # 只启动表情包管理任务 - asyncio.create_task(emoji_manager.start_periodic_check(interval_MINS=global_config.EMOJI_CHECK_INTERVAL)) + asyncio.create_task(emoji_manager.start_periodic_check()) await bot_schedule.initialize() bot_schedule.print_schedule() @@ -105,7 +105,7 @@ async def _(bot: Bot): _message_manager_started = True logger.success("-----------消息处理器已启动!-----------") - asyncio.create_task(emoji_manager._periodic_scan(interval_MINS=global_config.EMOJI_REGISTER_INTERVAL)) + asyncio.create_task(emoji_manager._periodic_scan()) logger.success("-----------开始偷表情包!-----------") asyncio.create_task(chat_manager._initialize()) asyncio.create_task(chat_manager._auto_save_task()) diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 57c387c0..a9e76648 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -57,9 +57,6 @@ class ChatBot: self.mood_manager = MoodManager.get_instance() # 获取情绪管理器单例 self.mood_manager.start_mood_update() # 启动情绪更新 - self.emoji_chance = 0.2 # 发送表情包的基础概率 - # self.message_streams = MessageStreamContainer() - async def _ensure_started(self): """确保所有任务已启动""" if not self._started: diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index 09ebe352..b16af913 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -17,40 +17,99 @@ class BotConfig: """机器人配置类""" INNER_VERSION: Version = None - - BOT_QQ: Optional[int] = 1 + MAI_VERSION: Version = None + + # bot + BOT_QQ: Optional[int] = 114514 BOT_NICKNAME: Optional[str] = None BOT_ALIAS_NAMES: List[str] = field(default_factory=list) # 别名,可以通过这个叫它 - - # 消息处理相关配置 - MIN_TEXT_LENGTH: int = 2 # 最小处理文本长度 - MAX_CONTEXT_SIZE: int = 15 # 上下文最大消息数 - emoji_chance: float = 0.2 # 发送表情包的基础概率 - - ENABLE_PIC_TRANSLATE: bool = True # 是否启用图片翻译 - + + # group talk_allowed_groups = set() talk_frequency_down_groups = set() - thinking_timeout: int = 100 # 思考时间 + ban_user_id = set() + + #personality + PROMPT_PERSONALITY = [ + "用一句话或几句话描述性格特点和其他特征", + "例如,是一个热爱国家热爱党的新时代好青年", + "例如,曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧" + ] + PERSONALITY_1: float = 0.6 # 第一种人格概率 + PERSONALITY_2: float = 0.3 # 第二种人格概率 + PERSONALITY_3: float = 0.1 # 第三种人格概率 + + # schedule + ENABLE_SCHEDULE_GEN: bool = False # 是否启用日程生成 + PROMPT_SCHEDULE_GEN = "无日程" + # message + MAX_CONTEXT_SIZE: int = 15 # 上下文最大消息数 + emoji_chance: float = 0.2 # 发送表情包的基础概率 + thinking_timeout: int = 120 # 思考时间 + max_response_length: int = 1024 # 最大回复长度 + + ban_words = set() + ban_msgs_regex = set() + + # willing + willing_mode: str = "classical" # 意愿模式 response_willing_amplifier: float = 1.0 # 回复意愿放大系数 response_interested_rate_amplifier: float = 1.0 # 回复兴趣度放大系数 - down_frequency_rate: float = 3.5 # 降低回复频率的群组回复意愿降低系数 - - ban_user_id = set() + down_frequency_rate: float = 3 # 降低回复频率的群组回复意愿降低系数 + + # response + MODEL_R1_PROBABILITY: float = 0.8 # R1模型概率 + MODEL_V3_PROBABILITY: float = 0.1 # V3模型概率 + MODEL_R1_DISTILL_PROBABILITY: float = 0.1 # R1蒸馏模型概率 + # emoji EMOJI_CHECK_INTERVAL: int = 120 # 表情包检查间隔(分钟) EMOJI_REGISTER_INTERVAL: int = 10 # 表情包注册间隔(分钟) EMOJI_SAVE: bool = True # 偷表情包 EMOJI_CHECK: bool = False # 是否开启过滤 EMOJI_CHECK_PROMPT: str = "符合公序良俗" # 表情包过滤要求 - ban_words = set() - ban_msgs_regex = set() + # memory + build_memory_interval: int = 600 # 记忆构建间隔(秒) + memory_build_distribution: list = field( + default_factory=lambda: [4,2,0.6,24,8,0.4] + ) # 记忆构建分布,参数:分布1均值,标准差,权重,分布2均值,标准差,权重 + build_memory_sample_num: int = 10 # 记忆构建采样数量 + build_memory_sample_length: int = 20 # 记忆构建采样长度 + memory_compress_rate: float = 0.1 # 记忆压缩率 + + forget_memory_interval: int = 600 # 记忆遗忘间隔(秒) + memory_forget_time: int = 24 # 记忆遗忘时间(小时) + memory_forget_percentage: float = 0.01 # 记忆遗忘比例 + + memory_ban_words: list = field( + default_factory=lambda: ["表情包", "图片", "回复", "聊天记录"] + ) # 添加新的配置项默认值 - max_response_length: int = 1024 # 最大回复长度 + # mood + mood_update_interval: float = 1.0 # 情绪更新间隔 单位秒 + mood_decay_rate: float = 0.95 # 情绪衰减率 + mood_intensity_factor: float = 0.7 # 情绪强度因子 + + # keywords + keywords_reaction_rules = [] # 关键词回复规则 + + # chinese_typo + chinese_typo_enable = True # 是否启用中文错别字生成器 + chinese_typo_error_rate = 0.03 # 单字替换概率 + chinese_typo_min_freq = 7 # 最小字频阈值 + chinese_typo_tone_error_rate = 0.2 # 声调错误概率 + chinese_typo_word_replace_rate = 0.02 # 整词替换概率 - remote_enable: bool = False # 是否启用远程控制 + # remote + remote_enable: bool = True # 是否启用远程控制 + + # experimental + enable_friend_chat: bool = False # 是否启用好友聊天 + enable_think_flow: bool = False # 是否启用思考流程 + + # 模型配置 llm_reasoning: Dict[str, str] = field(default_factory=lambda: {}) @@ -59,61 +118,13 @@ class BotConfig: llm_topic_judge: Dict[str, str] = field(default_factory=lambda: {}) llm_summary_by_topic: Dict[str, str] = field(default_factory=lambda: {}) llm_emotion_judge: Dict[str, str] = field(default_factory=lambda: {}) - llm_outer_world: Dict[str, str] = field(default_factory=lambda: {}) embedding: Dict[str, str] = field(default_factory=lambda: {}) vlm: Dict[str, str] = field(default_factory=lambda: {}) moderation: Dict[str, str] = field(default_factory=lambda: {}) - MODEL_R1_PROBABILITY: float = 0.8 # R1模型概率 - MODEL_V3_PROBABILITY: float = 0.1 # V3模型概率 - MODEL_R1_DISTILL_PROBABILITY: float = 0.1 # R1蒸馏模型概率 + # 实验性 + llm_outer_world: Dict[str, str] = field(default_factory=lambda: {}) - # enable_advance_output: bool = False # 是否启用高级输出 - enable_kuuki_read: bool = True # 是否启用读空气功能 - # enable_debug_output: bool = False # 是否启用调试输出 - enable_friend_chat: bool = False # 是否启用好友聊天 - - mood_update_interval: float = 1.0 # 情绪更新间隔 单位秒 - mood_decay_rate: float = 0.95 # 情绪衰减率 - mood_intensity_factor: float = 0.7 # 情绪强度因子 - - willing_mode: str = "classical" # 意愿模式 - - keywords_reaction_rules = [] # 关键词回复规则 - - chinese_typo_enable = True # 是否启用中文错别字生成器 - chinese_typo_error_rate = 0.03 # 单字替换概率 - chinese_typo_min_freq = 7 # 最小字频阈值 - chinese_typo_tone_error_rate = 0.2 # 声调错误概率 - chinese_typo_word_replace_rate = 0.02 # 整词替换概率 - - # 默认人设 - PROMPT_PERSONALITY = [ - "曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧", - "是一个女大学生,你有黑色头发,你会刷小红书", - "是一个女大学生,你会刷b站,对ACG文化感兴趣", - ] - - PROMPT_SCHEDULE_GEN = "一个曾经学习地质,现在学习心理学和脑科学的女大学生,喜欢刷qq,贴吧,知乎和小红书" - - PERSONALITY_1: float = 0.6 # 第一种人格概率 - PERSONALITY_2: float = 0.3 # 第二种人格概率 - PERSONALITY_3: float = 0.1 # 第三种人格概率 - - build_memory_interval: int = 600 # 记忆构建间隔(秒) - - forget_memory_interval: int = 600 # 记忆遗忘间隔(秒) - memory_forget_time: int = 24 # 记忆遗忘时间(小时) - memory_forget_percentage: float = 0.01 # 记忆遗忘比例 - memory_compress_rate: float = 0.1 # 记忆压缩率 - build_memory_sample_num: int = 10 # 记忆构建采样数量 - build_memory_sample_length: int = 20 # 记忆构建采样长度 - memory_build_distribution: list = field( - default_factory=lambda: [4,2,0.6,24,8,0.4] - ) # 记忆构建分布,参数:分布1均值,标准差,权重,分布2均值,标准差,权重 - memory_ban_words: list = field( - default_factory=lambda: ["表情包", "图片", "回复", "聊天记录"] - ) # 添加新的配置项默认值 @staticmethod def get_config_dir() -> str: @@ -184,13 +195,17 @@ class BotConfig: if len(personality) >= 2: logger.debug(f"载入自定义人格:{personality}") config.PROMPT_PERSONALITY = personality_config.get("prompt_personality", config.PROMPT_PERSONALITY) - logger.info(f"载入自定义日程prompt:{personality_config.get('prompt_schedule', config.PROMPT_SCHEDULE_GEN)}") - config.PROMPT_SCHEDULE_GEN = personality_config.get("prompt_schedule", config.PROMPT_SCHEDULE_GEN) - + if config.INNER_VERSION in SpecifierSet(">=0.0.2"): config.PERSONALITY_1 = personality_config.get("personality_1_probability", config.PERSONALITY_1) config.PERSONALITY_2 = personality_config.get("personality_2_probability", config.PERSONALITY_2) config.PERSONALITY_3 = personality_config.get("personality_3_probability", config.PERSONALITY_3) + + def schedule(parent: dict): + schedule_config = parent["schedule"] + config.ENABLE_SCHEDULE_GEN = schedule_config.get("enable_schedule_gen", config.ENABLE_SCHEDULE_GEN) + config.PROMPT_SCHEDULE_GEN = schedule_config.get("prompt_schedule_gen", config.PROMPT_SCHEDULE_GEN) + logger.info(f"载入自定义日程prompt:{schedule_config.get('prompt_schedule_gen', config.PROMPT_SCHEDULE_GEN)}") def emoji(parent: dict): emoji_config = parent["emoji"] @@ -200,10 +215,6 @@ class BotConfig: config.EMOJI_SAVE = emoji_config.get("auto_save", config.EMOJI_SAVE) config.EMOJI_CHECK = emoji_config.get("enable_check", config.EMOJI_CHECK) - def cq_code(parent: dict): - cq_code_config = parent["cq_code"] - config.ENABLE_PIC_TRANSLATE = cq_code_config.get("enable_pic_translate", config.ENABLE_PIC_TRANSLATE) - def bot(parent: dict): # 机器人基础配置 bot_config = parent["bot"] @@ -226,6 +237,11 @@ class BotConfig: def willing(parent: dict): willing_config = parent["willing"] config.willing_mode = willing_config.get("willing_mode", config.willing_mode) + + if config.INNER_VERSION in SpecifierSet(">=0.0.11"): + config.response_willing_amplifier = willing_config.get("response_willing_amplifier", config.response_willing_amplifier) + config.response_interested_rate_amplifier = willing_config.get("response_interested_rate_amplifier", config.response_interested_rate_amplifier) + config.down_frequency_rate = willing_config.get("down_frequency_rate", config.down_frequency_rate) def model(parent: dict): # 加载模型配置 @@ -238,10 +254,10 @@ class BotConfig: "llm_topic_judge", "llm_summary_by_topic", "llm_emotion_judge", - "llm_outer_world", "vlm", "embedding", "moderation", + "llm_outer_world", ] for item in config_list: @@ -282,12 +298,11 @@ class BotConfig: # 如果 列表中的项目在 model_config 中,利用反射来设置对应项目 setattr(config, item, cfg_target) else: - logger.error(f"模型 {item} 在config中不存在,请检查") - raise KeyError(f"模型 {item} 在config中不存在,请检查") + logger.error(f"模型 {item} 在config中不存在,请检查,或尝试更新配置文件") + raise KeyError(f"模型 {item} 在config中不存在,请检查,或尝试更新配置文件") def message(parent: dict): msg_config = parent["message"] - config.MIN_TEXT_LENGTH = msg_config.get("min_text_length", config.MIN_TEXT_LENGTH) config.MAX_CONTEXT_SIZE = msg_config.get("max_context_size", config.MAX_CONTEXT_SIZE) config.emoji_chance = msg_config.get("emoji_chance", config.emoji_chance) config.ban_words = msg_config.get("ban_words", config.ban_words) @@ -304,7 +319,9 @@ class BotConfig: if config.INNER_VERSION in SpecifierSet(">=0.0.6"): config.ban_msgs_regex = msg_config.get("ban_msgs_regex", config.ban_msgs_regex) - + + if config.INNER_VERSION in SpecifierSet(">=0.0.11"): + config.max_response_length = msg_config.get("max_response_length", config.max_response_length) def memory(parent: dict): memory_config = parent["memory"] config.build_memory_interval = memory_config.get("build_memory_interval", config.build_memory_interval) @@ -368,13 +385,9 @@ class BotConfig: config.talk_frequency_down_groups = set(groups_config.get("talk_frequency_down", [])) config.ban_user_id = set(groups_config.get("ban_user_id", [])) - def others(parent: dict): - others_config = parent["others"] - # config.enable_advance_output = others_config.get("enable_advance_output", config.enable_advance_output) - config.enable_kuuki_read = others_config.get("enable_kuuki_read", config.enable_kuuki_read) - if config.INNER_VERSION in SpecifierSet(">=0.0.7"): - # config.enable_debug_output = others_config.get("enable_debug_output", config.enable_debug_output) - config.enable_friend_chat = others_config.get("enable_friend_chat", config.enable_friend_chat) + def experimental(parent: dict): + experimental_config = parent["experimental"] + config.enable_friend_chat = experimental_config.get("enable_friend_chat", config.enable_friend_chat) # 版本表达式:>=1.0.0,<2.0.0 # 允许字段:func: method, support: str, notice: str, necessary: bool @@ -382,21 +395,21 @@ class BotConfig: # 例如:"notice": "personality 将在 1.3.2 后被移除",那么在有效版本中的用户就会虽然可以 # 正常执行程序,但是会看到这条自定义提示 include_configs = { - "personality": {"func": personality, "support": ">=0.0.0"}, - "emoji": {"func": emoji, "support": ">=0.0.0"}, - "cq_code": {"func": cq_code, "support": ">=0.0.0"}, "bot": {"func": bot, "support": ">=0.0.0"}, - "response": {"func": response, "support": ">=0.0.0"}, - "willing": {"func": willing, "support": ">=0.0.9", "necessary": False}, - "model": {"func": model, "support": ">=0.0.0"}, + "groups": {"func": groups, "support": ">=0.0.0"}, + "personality": {"func": personality, "support": ">=0.0.0"}, + "schedule": {"func": schedule, "support": ">=0.0.11", "necessary": False}, "message": {"func": message, "support": ">=0.0.0"}, + "willing": {"func": willing, "support": ">=0.0.9", "necessary": False}, + "emoji": {"func": emoji, "support": ">=0.0.0"}, + "response": {"func": response, "support": ">=0.0.0"}, + "model": {"func": model, "support": ">=0.0.0"}, "memory": {"func": memory, "support": ">=0.0.0", "necessary": False}, "mood": {"func": mood, "support": ">=0.0.0"}, "remote": {"func": remote, "support": ">=0.0.10", "necessary": False}, "keywords_reaction": {"func": keywords_reaction, "support": ">=0.0.2", "necessary": False}, "chinese_typo": {"func": chinese_typo, "support": ">=0.0.3", "necessary": False}, - "groups": {"func": groups, "support": ">=0.0.0"}, - "others": {"func": others, "support": ">=0.0.0"}, + "experimental": {"func": experimental, "support": ">=0.0.11", "necessary": False}, } # 原地修改,将 字符串版本表达式 转换成 版本对象 @@ -454,14 +467,13 @@ class BotConfig: # 获取配置文件路径 bot_config_floder_path = BotConfig.get_config_dir() -logger.debug(f"正在品鉴配置文件目录: {bot_config_floder_path}") +logger.info(f"正在品鉴配置文件目录: {bot_config_floder_path}") bot_config_path = os.path.join(bot_config_floder_path, "bot_config.toml") if os.path.exists(bot_config_path): # 如果开发环境配置文件不存在,则使用默认配置文件 - logger.debug(f"异常的新鲜,异常的美味: {bot_config_path}") - logger.info("使用bot配置文件") + logger.info(f"异常的新鲜,异常的美味: {bot_config_path}") else: # 配置文件不存在 logger.error("配置文件不存在,请检查路径: {bot_config_path}") diff --git a/src/plugins/chat/emoji_manager.py b/src/plugins/chat/emoji_manager.py index 683a3773..20a5c3b1 100644 --- a/src/plugins/chat/emoji_manager.py +++ b/src/plugins/chat/emoji_manager.py @@ -340,12 +340,12 @@ class EmojiManager: except Exception: logger.exception("[错误] 扫描表情包失败") - async def _periodic_scan(self, interval_MINS: int = 10): + async def _periodic_scan(self): """定期扫描新表情包""" while True: logger.info("[扫描] 开始扫描新表情包...") await self.scan_new_emojis() - await asyncio.sleep(interval_MINS * 60) # 每600秒扫描一次 + await asyncio.sleep(global_config.EMOJI_CHECK_INTERVAL * 60) def check_emoji_file_integrity(self): """检查表情包文件完整性 @@ -418,10 +418,10 @@ class EmojiManager: logger.error(f"[错误] 检查表情包完整性失败: {str(e)}") logger.error(traceback.format_exc()) - async def start_periodic_check(self, interval_MINS: int = 120): + async def start_periodic_check(self): while True: self.check_emoji_file_integrity() - await asyncio.sleep(interval_MINS * 60) + await asyncio.sleep(global_config.EMOJI_CHECK_INTERVAL * 60) # 创建全局单例 diff --git a/src/plugins/willing/mode_custom.py b/src/plugins/willing/mode_custom.py index e69de29b..a131b576 100644 --- a/src/plugins/willing/mode_custom.py +++ b/src/plugins/willing/mode_custom.py @@ -0,0 +1,102 @@ +import asyncio +from typing import Dict +from ..chat.chat_stream import ChatStream + + +class WillingManager: + def __init__(self): + self.chat_reply_willing: Dict[str, float] = {} # 存储每个聊天流的回复意愿 + self._decay_task = None + self._started = False + + async def _decay_reply_willing(self): + """定期衰减回复意愿""" + while True: + await asyncio.sleep(1) + for chat_id in self.chat_reply_willing: + self.chat_reply_willing[chat_id] = max(0, self.chat_reply_willing[chat_id] * 0.9) + + def get_willing(self, chat_stream: ChatStream) -> float: + """获取指定聊天流的回复意愿""" + if chat_stream: + return self.chat_reply_willing.get(chat_stream.stream_id, 0) + return 0 + + def set_willing(self, chat_id: str, willing: float): + """设置指定聊天流的回复意愿""" + self.chat_reply_willing[chat_id] = willing + + async def change_reply_willing_received( + self, + chat_stream: ChatStream, + is_mentioned_bot: bool = False, + config=None, + is_emoji: bool = False, + interested_rate: float = 0, + sender_id: str = None, + ) -> float: + """改变指定聊天流的回复意愿并返回回复概率""" + chat_id = chat_stream.stream_id + current_willing = self.chat_reply_willing.get(chat_id, 0) + + interested_rate = interested_rate * config.response_interested_rate_amplifier + + + if interested_rate > 0.4: + current_willing += interested_rate - 0.3 + + if is_mentioned_bot and current_willing < 1.0: + current_willing += 1 + elif is_mentioned_bot: + current_willing += 0.05 + + if is_emoji: + current_willing *= 0.2 + + self.chat_reply_willing[chat_id] = min(current_willing, 3.0) + + reply_probability = min(max((current_willing - 0.5), 0.01) * config.response_willing_amplifier * 2, 1) + + # 检查群组权限(如果是群聊) + if chat_stream.group_info and config: + if chat_stream.group_info.group_id not in config.talk_allowed_groups: + current_willing = 0 + reply_probability = 0 + + if chat_stream.group_info.group_id in config.talk_frequency_down_groups: + reply_probability = reply_probability / config.down_frequency_rate + + return reply_probability + + def change_reply_willing_sent(self, chat_stream: ChatStream): + """发送消息后降低聊天流的回复意愿""" + if chat_stream: + chat_id = chat_stream.stream_id + current_willing = self.chat_reply_willing.get(chat_id, 0) + self.chat_reply_willing[chat_id] = max(0, current_willing - 1.8) + + def change_reply_willing_not_sent(self, chat_stream: ChatStream): + """未发送消息后降低聊天流的回复意愿""" + if chat_stream: + chat_id = chat_stream.stream_id + current_willing = self.chat_reply_willing.get(chat_id, 0) + self.chat_reply_willing[chat_id] = max(0, current_willing - 0) + + def change_reply_willing_after_sent(self, chat_stream: ChatStream): + """发送消息后提高聊天流的回复意愿""" + if chat_stream: + chat_id = chat_stream.stream_id + current_willing = self.chat_reply_willing.get(chat_id, 0) + if current_willing < 1: + self.chat_reply_willing[chat_id] = min(1, current_willing + 0.4) + + async def ensure_started(self): + """确保衰减任务已启动""" + if not self._started: + if self._decay_task is None: + self._decay_task = asyncio.create_task(self._decay_reply_willing()) + self._started = True + + +# 创建全局实例 +willing_manager = WillingManager() diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index bf7118d1..dcd3403a 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,6 +1,10 @@ [inner] version = "0.0.11" +[mai_version] +version = "0.6.0" +version-fix = "snapshot-1" + #以下是给开发人员阅读的,一般用户不需要阅读 #如果你想要修改配置文件,请在修改后将version的值进行变更 #如果新增项目,请在BotConfig类下新增相应的变量 @@ -14,30 +18,37 @@ version = "0.0.11" # config.memory_ban_words = set(memory_config.get("memory_ban_words", [])) [bot] -qq = 123 +qq = 114514 nickname = "麦麦" alias_names = ["麦叠", "牢麦"] +[groups] +talk_allowed = [ + 123, + 123, +] #可以回复消息的群号码 +talk_frequency_down = [] #降低回复频率的群号码 +ban_user_id = [] #禁止回复和读取消息的QQ号 + [personality] prompt_personality = [ "用一句话或几句话描述性格特点和其他特征", - "用一句话或几句话描述性格特点和其他特征", - "例如,是一个热爱国家热爱党的新时代好青年" + "例如,是一个热爱国家热爱党的新时代好青年", + "例如,曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧" ] personality_1_probability = 0.7 # 第一种人格出现概率 -personality_2_probability = 0.2 # 第二种人格出现概率 +personality_2_probability = 0.2 # 第二种人格出现概率,可以为0 personality_3_probability = 0.1 # 第三种人格出现概率,请确保三个概率相加等于1 -prompt_schedule = "用一句话或几句话描述描述性格特点和其他特征" + +[schedule] +enable_schedule_gen = true # 是否启用日程表 +prompt_schedule_gen = "用几句话描述描述性格特点或行动规律,这个特征会用来生成日程表" [message] -min_text_length = 2 # 与麦麦聊天时麦麦只会回答文本大于等于此数的消息 -max_context_size = 15 # 麦麦获得的上文数量 +max_context_size = 15 # 麦麦获得的上文数量,建议15,太短太长都会导致脑袋尖尖 emoji_chance = 0.2 # 麦麦使用表情包的概率 -thinking_timeout = 120 # 麦麦思考时间 - -response_willing_amplifier = 1 # 麦麦回复意愿放大系数,一般为1 -response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数,听到记忆里的内容时放大系数 -down_frequency_rate = 3 # 降低回复频率的群组回复意愿降低系数 除法 +thinking_timeout = 120 # 麦麦最长思考时间,超过这个时间的思考会放弃 +max_response_length = 1024 # 麦麦回答的最大token数 ban_words = [ # "403","张三" ] @@ -49,26 +60,25 @@ ban_msgs_regex = [ # "\\[CQ:at,qq=\\d+\\]" # 匹配@ ] -[emoji] -check_interval = 300 # 检查表情包的时间间隔 -register_interval = 20 # 注册表情包的时间间隔 -auto_save = true # 自动偷表情包 -enable_check = false # 是否启用表情包过滤 -check_prompt = "符合公序良俗" # 表情包过滤要求 - -[cq_code] -enable_pic_translate = false +[willing] +willing_mode = "classical" # 回复意愿模式 经典模式 +# willing_mode = "dynamic" # 动态模式(可能不兼容) +# willing_mode = "custom" # 自定义模式(可自行调整 +response_willing_amplifier = 1 # 麦麦回复意愿放大系数,一般为1 +response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数,听到记忆里的内容时放大系数 +down_frequency_rate = 3 # 降低回复频率的群组回复意愿降低系数 除法 [response] model_r1_probability = 0.8 # 麦麦回答时选择主要回复模型1 模型的概率 model_v3_probability = 0.1 # 麦麦回答时选择次要回复模型2 模型的概率 model_r1_distill_probability = 0.1 # 麦麦回答时选择次要回复模型3 模型的概率 -max_response_length = 1024 # 麦麦回答的最大token数 -[willing] -willing_mode = "classical" # 回复意愿模式 经典模式 -# willing_mode = "dynamic" # 动态模式(可能不兼容) -# willing_mode = "custom" # 自定义模式(可自行调整 +[emoji] +check_interval = 15 # 检查破损表情包的时间间隔(分钟) +register_interval = 60 # 注册表情包的时间间隔(分钟) +auto_save = true # 是否保存表情包和图片 +enable_check = false # 是否启用表情包过滤 +check_prompt = "符合公序良俗" # 表情包过滤要求 [memory] build_memory_interval = 2000 # 记忆构建间隔 单位秒 间隔越低,麦麦学习越多,但是冗余信息也会增多 @@ -81,7 +91,6 @@ forget_memory_interval = 1000 # 记忆遗忘间隔 单位秒 间隔越低, memory_forget_time = 24 #多长时间后的记忆会被遗忘 单位小时 memory_forget_percentage = 0.01 # 记忆遗忘比例 控制记忆遗忘程度 越大遗忘越多 建议保持默认 - memory_ban_words = [ #不希望记忆的词 # "403","张三" ] @@ -106,26 +115,17 @@ reaction = "回答“测试成功”" [chinese_typo] enable = true # 是否启用中文错别字生成器 -error_rate=0.002 # 单字替换概率 +error_rate=0.001 # 单字替换概率 min_freq=9 # 最小字频阈值 -tone_error_rate=0.2 # 声调错误概率 +tone_error_rate=0.1 # 声调错误概率 word_replace_rate=0.006 # 整词替换概率 -[others] -enable_kuuki_read = true # 是否启用读空气功能 -enable_friend_chat = false # 是否启用好友聊天 - -[groups] -talk_allowed = [ - 123, - 123, -] #可以回复消息的群 -talk_frequency_down = [] #降低回复频率的群 -ban_user_id = [] #禁止回复和读取消息的QQ号 - [remote] #发送统计信息,主要是看全球有多少只麦麦 enable = true +[experimental] +enable_friend_chat = false # 是否启用好友聊天 +enable_thinkflow = false # 是否启用思维流 #下面的模型若使用硅基流动则不需要更改,使用ds官方则改成.env.prod自定义的宏,使用自定义模型则选择定位相似的模型自己填写 #推理模型 @@ -188,3 +188,11 @@ pri_out = 0.35 [model.embedding] #嵌入 name = "BAAI/bge-m3" provider = "SILICONFLOW" + +#测试模型,给think_glow用,如果你没开实验性功能,随便写就行,但是要有 +[model.llm_outer_world] #外世界判断:建议使用qwen2.5 7b +# name = "Pro/Qwen/Qwen2.5-7B-Instruct" +name = "Qwen/Qwen2.5-7B-Instruct" +provider = "SILICONFLOW" +pri_in = 0 +pri_out = 0 \ No newline at end of file From 51990391fd5e6bedb189e042c299f70ba41e55d8 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 25 Mar 2025 22:27:38 +0800 Subject: [PATCH 06/18] =?UTF-8?q?better=20=E6=96=B0=E5=A2=9E=E4=BA=86?= =?UTF-8?q?=E5=88=86=E5=89=B2=E5=99=A8=EF=BC=8C=E8=A1=A8=E6=83=85=E6=83=A9?= =?UTF-8?q?=E7=BD=9A=E7=B3=BB=E6=95=B0=E7=9A=84=E8=87=AA=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/bot_config_test.toml | 179 -------------------------- src/plugins/chat/config.py | 16 ++- src/plugins/chat/utils.py | 29 +++-- src/plugins/willing/mode_classical.py | 3 +- template/bot_config_template.toml | 9 +- 5 files changed, 40 insertions(+), 196 deletions(-) delete mode 100644 config/bot_config_test.toml diff --git a/config/bot_config_test.toml b/config/bot_config_test.toml deleted file mode 100644 index dd01bfde..00000000 --- a/config/bot_config_test.toml +++ /dev/null @@ -1,179 +0,0 @@ -[inner] -version = "0.0.10" - -[mai_version] -version = "0.6.0" -version-fix = "snapshot-1" - -#以下是给开发人员阅读的,一般用户不需要阅读 -#如果你想要修改配置文件,请在修改后将version的值进行变更 -#如果新增项目,请在BotConfig类下新增相应的变量 -#1.如果你修改的是[]层级项目,例如你新增了 [memory],那么请在config.py的 load_config函数中的include_configs字典中新增"内容":{ -#"func":memory, -#"support":">=0.0.0", #新的版本号 -#"necessary":False #是否必须 -#} -#2.如果你修改的是[]下的项目,例如你新增了[memory]下的 memory_ban_words ,那么请在config.py的 load_config函数中的 memory函数下新增版本判断: - # if config.INNER_VERSION in SpecifierSet(">=0.0.2"): - # config.memory_ban_words = set(memory_config.get("memory_ban_words", [])) - -[bot] -qq = 2814567326 -nickname = "麦麦" -alias_names = ['牢麦', '麦叠', '哈基麦'] - -[personality] -prompt_personality = ['曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧', '是一个女大学生,你有黑色头发,你会刷小红书', '是一个女大学生,你会刷b站,对ACG文化感兴趣'] -personality_1_probability = 0.7 # 第一种人格出现概率 -personality_2_probability = 0.1 # 第二种人格出现概率 -personality_3_probability = 0.2 # 第三种人格出现概率,请确保三个概率相加等于1 -prompt_schedule = "一个曾经学习地质,现在学习心理学和脑科学的女大学生,喜欢刷qq,贴吧,知乎和小红书" - -[message] -min_text_length = 2 # 与麦麦聊天时麦麦只会回答文本大于等于此数的消息 -max_context_size = 10 # 麦麦获得的上文数量 -emoji_chance = 0.2 # 麦麦使用表情包的概率 -thinking_timeout = 100 # 麦麦思考时间 - -response_willing_amplifier = 1 # 麦麦回复意愿放大系数,一般为1 -response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数,听到记忆里的内容时放大系数 -down_frequency_rate = 2 # 降低回复频率的群组回复意愿降低系数 -ban_words = [] - -ban_msgs_regex = [] - -[emoji] -check_interval = 120 # 检查表情包的时间间隔 -register_interval = 10 # 注册表情包的时间间隔 -auto_save = true # 自动偷表情包 -enable_check = false # 是否启用表情包过滤 -check_prompt = "符合公序良俗" # 表情包过滤要求 - -[cq_code] -enable_pic_translate = false - -[response] -model_r1_probability = 0.5 # 麦麦回答时选择主要回复模型1 模型的概率 -model_v3_probability = 0.5 # 麦麦回答时选择次要回复模型2 模型的概率 -model_r1_distill_probability = 0 # 麦麦回答时选择次要回复模型3 模型的概率 -max_response_length = 1024 # 麦麦回答的最大token数 - -[willing] -willing_mode = "classical" # 回复意愿模式 经典模式 -# willing_mode = "dynamic" # 动态模式(可能不兼容) -# willing_mode = "custom" # 自定义模式(可自行调整 - -[memory] -build_memory_interval = 3000 # 记忆构建间隔 单位秒 间隔越低,麦麦学习越多,但是冗余信息也会增多 -build_memory_distribution = [4, 4, 0.6, 48, 36, 0.4] # 记忆构建分布,参数:分布1均值,标准差,权重,分布2均值,标准差,权重 -build_memory_sample_num = 10 # 采样数量,数值越高记忆采样次数越多 -build_memory_sample_length = 30 # 采样长度,数值越高一段记忆内容越丰富 -memory_compress_rate = 0.1 # 记忆压缩率 控制记忆精简程度 建议保持默认,调高可以获得更多信息,但是冗余信息也会增多 - -forget_memory_interval = 300 # 记忆遗忘间隔 单位秒 间隔越低,麦麦遗忘越频繁,记忆更精简,但更难学习 -memory_forget_time = 24 #多长时间后的记忆会被遗忘 单位小时 -memory_forget_percentage = 0.005 # 记忆遗忘比例 控制记忆遗忘程度 越大遗忘越多 建议保持默认 - - -memory_ban_words = ['表情包', '图片', '回复', '聊天记录'] - -[mood] -mood_update_interval = 1.0 # 情绪更新间隔 单位秒 -mood_decay_rate = 0.95 # 情绪衰减率 -mood_intensity_factor = 1.0 # 情绪强度因子 - -[keywords_reaction] # 针对某个关键词作出反应 -enable = true # 关键词反应功能的总开关 - -[[keywords_reaction.rules]] -enable = true -keywords = [ "人机", "bot", "机器", "入机", "robot", "机器人",] -reaction = "有人说你是人机或者机器人,否定这一事实,攻击或反问他。注意,只有认为你是机器人才回复,否则不要否认" - -[[keywords_reaction.rules]] -enable = false -keywords = [ "测试关键词回复", "test", "",] -reaction = "回答“测试成功”" - -[chinese_typo] -enable = true # 是否启用中文错别字生成器 -error_rate=0.01 # 单字替换概率 -min_freq=7 # 最小字频阈值 -tone_error_rate=0.3 # 声调错误概率 -word_replace_rate=0.01 # 整词替换概率 - -[others] -enable_kuuki_read = true # 是否启用读空气功能 -enable_friend_chat = true # 是否启用好友聊天 - -[groups] -talk_allowed = [571780722,1022489779,534940728, 192194125, 851345375, 739044565, 766798517, 1030993430, 435591861, 708847644, 591693379, 571780722, 1028699246, 571780722, 1015816696] #可以回复消息的群 -talk_frequency_down = [1022489779, 571780722] #降低回复频率的群 -ban_user_id = [3488737411, 2732836727, 3878664193, 3799953254] #禁止回复和读取消息的QQ号 - -[remote] #发送统计信息,主要是看全球有多少只麦麦 -enable = true - -#下面的模型若使用硅基流动则不需要更改,使用ds官方则改成.env.prod自定义的宏,使用自定义模型则选择定位相似的模型自己填写 - -#推理模型 - -[model.llm_reasoning] #回复模型1 主要回复模型 -# name = "Pro/deepseek-ai/DeepSeek-R1" -name = "Qwen/QwQ-32B" -provider = "SILICONFLOW" -pri_in = 1.0 #模型的输入价格(非必填,可以记录消耗) -pri_out = 4.0 #模型的输出价格(非必填,可以记录消耗) - -[model.llm_reasoning_minor] #回复模型3 次要回复模型 -name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B" -provider = "SILICONFLOW" -pri_in = 1.26 #模型的输入价格(非必填,可以记录消耗) -pri_out = 1.26 #模型的输出价格(非必填,可以记录消耗) - -#非推理模型 - -[model.llm_normal] #V3 回复模型2 次要回复模型 -name = "Qwen/Qwen2.5-32B-Instruct" -provider = "SILICONFLOW" -pri_in = 1.26 #模型的输入价格(非必填,可以记录消耗) -pri_out = 1.26 #模型的输出价格(非必填,可以记录消耗) - -[model.llm_emotion_judge] #表情包判断 -name = "Qwen/Qwen2.5-14B-Instruct" -provider = "SILICONFLOW" -pri_in = 0.7 -pri_out = 0.7 - -[model.llm_topic_judge] #记忆主题判断:建议使用qwen2.5 7b -name = "Pro/Qwen/Qwen2.5-7B-Instruct" -# name = "Qwen/Qwen2-1.5B-Instruct" -provider = "SILICONFLOW" -pri_in = 0.35 -pri_out = 0.35 - -[model.llm_summary_by_topic] #概括模型,建议使用qwen2.5 32b 及以上 -name = "Qwen/Qwen2.5-32B-Instruct" -provider = "SILICONFLOW" -pri_in = 1.26 -pri_out = 1.26 - -[model.moderation] #内容审核,开发中 -name = "" -provider = "SILICONFLOW" -pri_in = 1.0 -pri_out = 2.0 - -# 识图模型 - -[model.vlm] #图像识别 -name = "Pro/Qwen/Qwen2.5-VL-7B-Instruct" -provider = "SILICONFLOW" -pri_in = 0.35 -pri_out = 0.35 - -#嵌入模型 - -[model.embedding] #嵌入 -name = "BAAI/bge-m3" -provider = "SILICONFLOW" diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index b16af913..54303b95 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -57,6 +57,7 @@ class BotConfig: response_willing_amplifier: float = 1.0 # 回复意愿放大系数 response_interested_rate_amplifier: float = 1.0 # 回复兴趣度放大系数 down_frequency_rate: float = 3 # 降低回复频率的群组回复意愿降低系数 + emoji_response_penalty: float = 0.0 # 表情包回复惩罚 # response MODEL_R1_PROBABILITY: float = 0.8 # R1模型概率 @@ -101,6 +102,11 @@ class BotConfig: chinese_typo_min_freq = 7 # 最小字频阈值 chinese_typo_tone_error_rate = 0.2 # 声调错误概率 chinese_typo_word_replace_rate = 0.02 # 整词替换概率 + + #response_spliter + enable_response_spliter = True # 是否启用回复分割器 + response_max_length = 100 # 回复允许的最大长度 + response_max_sentence_num = 3 # 回复允许的最大句子数 # remote remote_enable: bool = True # 是否启用远程控制 @@ -242,7 +248,8 @@ class BotConfig: config.response_willing_amplifier = willing_config.get("response_willing_amplifier", config.response_willing_amplifier) config.response_interested_rate_amplifier = willing_config.get("response_interested_rate_amplifier", config.response_interested_rate_amplifier) config.down_frequency_rate = willing_config.get("down_frequency_rate", config.down_frequency_rate) - + config.emoji_response_penalty = willing_config.get("emoji_response_penalty", config.emoji_response_penalty) + def model(parent: dict): # 加载模型配置 model_config: dict = parent["model"] @@ -378,6 +385,12 @@ class BotConfig: config.chinese_typo_word_replace_rate = chinese_typo_config.get( "word_replace_rate", config.chinese_typo_word_replace_rate ) + + def response_spliter(parent: dict): + response_spliter_config = parent["response_spliter"] + config.enable_response_spliter = response_spliter_config.get("enable_response_spliter", config.enable_response_spliter) + config.response_max_length = response_spliter_config.get("response_max_length", config.response_max_length) + config.response_max_sentence_num = response_spliter_config.get("response_max_sentence_num", config.response_max_sentence_num) def groups(parent: dict): groups_config = parent["groups"] @@ -409,6 +422,7 @@ class BotConfig: "remote": {"func": remote, "support": ">=0.0.10", "necessary": False}, "keywords_reaction": {"func": keywords_reaction, "support": ">=0.0.2", "necessary": False}, "chinese_typo": {"func": chinese_typo, "support": ">=0.0.3", "necessary": False}, + "response_spliter": {"func": response_spliter, "support": ">=0.0.11", "necessary": False}, "experimental": {"func": experimental, "support": ">=0.0.11", "necessary": False}, } diff --git a/src/plugins/chat/utils.py b/src/plugins/chat/utils.py index 0d63e7af..ef9878c4 100644 --- a/src/plugins/chat/utils.py +++ b/src/plugins/chat/utils.py @@ -244,21 +244,17 @@ def split_into_sentences_w_remove_punctuation(text: str) -> List[str]: List[str]: 分割后的句子列表 """ len_text = len(text) - if len_text < 5: + if len_text < 4: if random.random() < 0.01: return list(text) # 如果文本很短且触发随机条件,直接按字符分割 else: return [text] if len_text < 12: - split_strength = 0.3 + split_strength = 0.2 elif len_text < 32: - split_strength = 0.7 + split_strength = 0.6 else: - split_strength = 0.9 - # 先移除换行符 - # print(f"split_strength: {split_strength}") - - # print(f"处理前的文本: {text}") + split_strength = 0.7 # 检查是否为西文字符段落 if not is_western_paragraph(text): @@ -348,7 +344,7 @@ def random_remove_punctuation(text: str) -> str: for i, char in enumerate(text): if char == "。" and i == text_len - 1: # 结尾的句号 - if random.random() > 0.4: # 80%概率删除结尾句号 + if random.random() > 0.1: # 90%概率删除结尾句号 continue elif char == ",": rand = random.random() @@ -364,10 +360,12 @@ def random_remove_punctuation(text: str) -> str: def process_llm_response(text: str) -> List[str]: # processed_response = process_text_with_typos(content) # 对西文字符段落的回复长度设置为汉字字符的两倍 - if len(text) > 100 and not is_western_paragraph(text) : + max_length = global_config.response_max_length + max_sentence_num = global_config.response_max_sentence_num + if len(text) > max_length and not is_western_paragraph(text) : logger.warning(f"回复过长 ({len(text)} 字符),返回默认回复") return ["懒得说"] - elif len(text) > 200 : + elif len(text) > max_length * 2 : logger.warning(f"回复过长 ({len(text)} 字符),返回默认回复") return ["懒得说"] # 处理长消息 @@ -377,7 +375,10 @@ def process_llm_response(text: str) -> List[str]: tone_error_rate=global_config.chinese_typo_tone_error_rate, word_replace_rate=global_config.chinese_typo_word_replace_rate, ) - split_sentences = split_into_sentences_w_remove_punctuation(text) + if global_config.enable_response_spliter: + split_sentences = split_into_sentences_w_remove_punctuation(text) + else: + split_sentences = [text] sentences = [] for sentence in split_sentences: if global_config.chinese_typo_enable: @@ -389,14 +390,14 @@ def process_llm_response(text: str) -> List[str]: sentences.append(sentence) # 检查分割后的消息数量是否过多(超过3条) - if len(sentences) > 3: + if len(sentences) > max_sentence_num: logger.warning(f"分割后消息数量过多 ({len(sentences)} 条),返回默认回复") return [f"{global_config.BOT_NICKNAME}不知道哦"] return sentences -def calculate_typing_time(input_string: str, chinese_time: float = 0.4, english_time: float = 0.2) -> float: +def calculate_typing_time(input_string: str, chinese_time: float = 0.2, english_time: float = 0.1) -> float: """ 计算输入字符串所需的时间,中文和英文字符有不同的输入时间 input_string (str): 输入的字符串 diff --git a/src/plugins/willing/mode_classical.py b/src/plugins/willing/mode_classical.py index a131b576..a0ec90ff 100644 --- a/src/plugins/willing/mode_classical.py +++ b/src/plugins/willing/mode_classical.py @@ -1,6 +1,7 @@ import asyncio from typing import Dict from ..chat.chat_stream import ChatStream +from ..chat.config import global_config class WillingManager: @@ -51,7 +52,7 @@ class WillingManager: current_willing += 0.05 if is_emoji: - current_willing *= 0.2 + current_willing *= global_config.emoji_response_penalty self.chat_reply_willing[chat_id] = min(current_willing, 3.0) diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index dcd3403a..c8ce896e 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -67,6 +67,7 @@ willing_mode = "classical" # 回复意愿模式 经典模式 response_willing_amplifier = 1 # 麦麦回复意愿放大系数,一般为1 response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数,听到记忆里的内容时放大系数 down_frequency_rate = 3 # 降低回复频率的群组回复意愿降低系数 除法 +emoji_response_penalty = 0.1 # 表情包回复惩罚系数,设为0为不回复单个表情包,减少单独回复表情包的概率 [response] model_r1_probability = 0.8 # 麦麦回答时选择主要回复模型1 模型的概率 @@ -105,7 +106,7 @@ enable = true # 关键词反应功能的总开关 [[keywords_reaction.rules]] # 如果想要新增多个关键词,直接复制本条,修改keywords和reaction即可 enable = true # 是否启用此条(为了人类在未来AI战争能更好地识别AI(bushi),默认开启) -keywords = ["人机", "bot", "机器", "入机", "robot", "机器人"] # 会触发反应的关键词 +keywords = ["人机", "bot", "机器", "入机", "robot", "机器人","ai","AI"] # 会触发反应的关键词 reaction = "有人说你是人机或者机器人,否定这一事实,攻击或反问他。注意,只有认为你是机器人才回复,否则不要否认" # 触发之后添加的提示词 [[keywords_reaction.rules]] # 就像这样复制 @@ -120,6 +121,12 @@ min_freq=9 # 最小字频阈值 tone_error_rate=0.1 # 声调错误概率 word_replace_rate=0.006 # 整词替换概率 +[response_spliter] +enable_response_spliter = true # 是否启用回复分割器 +response_max_length = 100 # 回复允许的最大长度 +response_max_sentence_num = 4 # 回复允许的最大句子数 + + [remote] #发送统计信息,主要是看全球有多少只麦麦 enable = true From 0ea57c4a583d16cbfd1fa79fa27b450ed28914c3 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 25 Mar 2025 22:40:43 +0800 Subject: [PATCH 07/18] =?UTF-8?q?feat=20=E5=B0=86=E5=BF=83=E6=B5=81?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E4=BD=9C=E4=B8=BA=20=E5=AE=9E=E9=AA=8C?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/__init__.py | 13 ++++++----- src/plugins/chat/bot.py | 35 ++++++++++++++++++----------- src/plugins/chat/config.py | 4 ++++ src/plugins/chat/prompt_builder.py | 5 ++++- src/plugins/moods/moods.py | 2 +- src/think_flow_demo/current_mind.py | 2 +- src/think_flow_demo/heartflow.py | 2 +- template/bot_config_template.toml | 18 +++++++++++++-- 8 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/plugins/chat/__init__.py b/src/plugins/chat/__init__.py index 713f1d37..d8a41fe8 100644 --- a/src/plugins/chat/__init__.py +++ b/src/plugins/chat/__init__.py @@ -51,7 +51,10 @@ async def start_think_flow(): try: outer_world_task = asyncio.create_task(outer_world.open_eyes()) logger.success("大脑和外部世界启动成功") - return outer_world_task + # 启动心流系统 + heartflow_task = asyncio.create_task(subheartflow_manager.heartflow_start_working()) + logger.success("心流系统启动成功") + return outer_world_task, heartflow_task except Exception as e: logger.error(f"启动大脑和外部世界失败: {e}") raise @@ -70,11 +73,9 @@ async def start_background_tasks(): logger.success("情绪管理器启动成功") # 启动大脑和外部世界 - await start_think_flow() - - # 启动心流系统 - heartflow_task = asyncio.create_task(subheartflow_manager.heartflow_start_working()) - logger.success("心流系统启动成功") + if global_config.enable_think_flow: + logger.success("启动测试功能:心流系统") + await start_think_flow() # 只启动表情包管理任务 asyncio.create_task(emoji_manager.start_periodic_check()) diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index a9e76648..e8937521 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -91,9 +91,11 @@ class ChatBot: ) message.update_chat_stream(chat) + #创建 心流 观察 - await outer_world.check_and_add_new_observe() - subheartflow_manager.create_subheartflow(chat.stream_id) + if global_config.enable_think_flow: + await outer_world.check_and_add_new_observe() + subheartflow_manager.create_subheartflow(chat.stream_id) await relationship_manager.update_relationship( @@ -142,10 +144,14 @@ class ChatBot: interested_rate=interested_rate, sender_id=str(message.message_info.user_info.user_id), ) - current_willing_old = willing_manager.get_willing(chat_stream=chat) - current_willing_new = (subheartflow_manager.get_subheartflow(chat.stream_id).current_state.willing-5)/4 - print(f"旧回复意愿:{current_willing_old},新回复意愿:{current_willing_new}") - current_willing = (current_willing_old + current_willing_new) / 2 + + if global_config.enable_think_flow: + current_willing_old = willing_manager.get_willing(chat_stream=chat) + current_willing_new = (subheartflow_manager.get_subheartflow(chat.stream_id).current_state.willing-5)/4 + print(f"旧回复意愿:{current_willing_old},新回复意愿:{current_willing_new}") + current_willing = (current_willing_old + current_willing_new) / 2 + else: + current_willing = willing_manager.get_willing(chat_stream=chat) logger.info( f"[{current_time}][{chat.group_info.group_name if chat.group_info else '私聊'}]" @@ -185,13 +191,16 @@ class ChatBot: # print(f"response: {response}") if response: stream_id = message.chat_stream.stream_id - chat_talking_prompt = "" - if stream_id: - chat_talking_prompt = get_recent_group_detailed_plain_text( - stream_id, limit=global_config.MAX_CONTEXT_SIZE, combine=True - ) - - await subheartflow_manager.get_subheartflow(stream_id).do_after_reply(response,chat_talking_prompt) + + if global_config.enable_think_flow: + chat_talking_prompt = "" + if stream_id: + chat_talking_prompt = get_recent_group_detailed_plain_text( + stream_id, limit=global_config.MAX_CONTEXT_SIZE, combine=True + ) + await subheartflow_manager.get_subheartflow(stream_id).do_after_reply(response,chat_talking_prompt) + + # print(f"有response: {response}") container = message_manager.get_container(chat.stream_id) thinking_message = None diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index 54303b95..503ba0dc 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -130,6 +130,8 @@ class BotConfig: # 实验性 llm_outer_world: Dict[str, str] = field(default_factory=lambda: {}) + llm_sub_heartflow: Dict[str, str] = field(default_factory=lambda: {}) + llm_heartflow: Dict[str, str] = field(default_factory=lambda: {}) @staticmethod @@ -265,6 +267,8 @@ class BotConfig: "embedding", "moderation", "llm_outer_world", + "llm_sub_heartflow", + "llm_heartflow", ] for item in config_list: diff --git a/src/plugins/chat/prompt_builder.py b/src/plugins/chat/prompt_builder.py index b03e6b04..e6bdaf97 100644 --- a/src/plugins/chat/prompt_builder.py +++ b/src/plugins/chat/prompt_builder.py @@ -37,7 +37,10 @@ class PromptBuilder: ) # outer_world_info = outer_world.outer_world_info - current_mind_info = subheartflow_manager.get_subheartflow(stream_id).current_mind + if global_config.enable_think_flow: + current_mind_info = subheartflow_manager.get_subheartflow(stream_id).current_mind + else: + current_mind_info = "" relation_prompt = "" for person in who_chat_in_group: diff --git a/src/plugins/moods/moods.py b/src/plugins/moods/moods.py index b09e5816..3e977d02 100644 --- a/src/plugins/moods/moods.py +++ b/src/plugins/moods/moods.py @@ -122,7 +122,7 @@ class MoodManager: time_diff = current_time - self.last_update # Valence 向中性(0)回归 - valence_target = -0.2 + valence_target = 0 self.current_mood.valence = valence_target + (self.current_mood.valence - valence_target) * math.exp( -self.decay_rate_valence * time_diff ) diff --git a/src/think_flow_demo/current_mind.py b/src/think_flow_demo/current_mind.py index 09634cf2..2446d66d 100644 --- a/src/think_flow_demo/current_mind.py +++ b/src/think_flow_demo/current_mind.py @@ -21,7 +21,7 @@ class SubHeartflow: self.current_mind = "" self.past_mind = [] self.current_state : CuttentState = CuttentState() - self.llm_model = LLM_request(model=global_config.llm_topic_judge, temperature=0.7, max_tokens=600, request_type="sub_heart_flow") + self.llm_model = LLM_request(model=global_config.llm_sub_heartflow, temperature=0.7, max_tokens=600, request_type="sub_heart_flow") self.outer_world = None self.main_heartflow_info = "" diff --git a/src/think_flow_demo/heartflow.py b/src/think_flow_demo/heartflow.py index 696641cb..e455e197 100644 --- a/src/think_flow_demo/heartflow.py +++ b/src/think_flow_demo/heartflow.py @@ -21,7 +21,7 @@ class Heartflow: self.current_mind = "你什么也没想" self.past_mind = [] self.current_state : CuttentState = CuttentState() - self.llm_model = LLM_request(model=global_config.llm_topic_judge, temperature=0.6, max_tokens=1000, request_type="heart_flow") + self.llm_model = LLM_request(model=global_config.llm_heartflow, temperature=0.6, max_tokens=1000, request_type="heart_flow") self._subheartflows = {} self.active_subheartflows_nums = 0 diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index c8ce896e..2359b678 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -132,7 +132,7 @@ enable = true [experimental] enable_friend_chat = false # 是否启用好友聊天 -enable_thinkflow = false # 是否启用思维流 +enable_think_flow = false # 是否启用思维流 #下面的模型若使用硅基流动则不需要更改,使用ds官方则改成.env.prod自定义的宏,使用自定义模型则选择定位相似的模型自己填写 #推理模型 @@ -202,4 +202,18 @@ provider = "SILICONFLOW" name = "Qwen/Qwen2.5-7B-Instruct" provider = "SILICONFLOW" pri_in = 0 -pri_out = 0 \ No newline at end of file +pri_out = 0 + +[model.llm_sub_heartflow] #心流:建议使用qwen2.5 7b +# name = "Pro/Qwen/Qwen2.5-7B-Instruct" +name = "Qwen/Qwen2.5-32B-Instruct" +provider = "SILICONFLOW" +pri_in = 1.26 +pri_out = 1.26 + +[model.llm_heartflow] #心流:建议使用qwen2.5 32b +# name = "Pro/Qwen/Qwen2.5-7B-Instruct" +name = "Qwen/Qwen2.5-32B-Instruct" +provider = "SILICONFLOW" +pri_in = 1.26 +pri_out = 1.26 \ No newline at end of file From 83ee182bfe36a15c145ddcef1c8a640b1b4425f1 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 25 Mar 2025 22:56:39 +0800 Subject: [PATCH 08/18] fix ruff --- src/plugins/chat/config.py | 21 ++++++++++++++------- src/plugins/chat/prompt_builder.py | 7 +++---- src/plugins/chat/relationship_manager.py | 5 +++-- src/think_flow_demo/current_mind.py | 22 ++++++++++++---------- src/think_flow_demo/heartflow.py | 10 ++++++---- src/think_flow_demo/outer_world.py | 12 +++++++----- template/bot_config_template.toml | 2 +- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index 503ba0dc..2b73996f 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -213,7 +213,8 @@ class BotConfig: schedule_config = parent["schedule"] config.ENABLE_SCHEDULE_GEN = schedule_config.get("enable_schedule_gen", config.ENABLE_SCHEDULE_GEN) config.PROMPT_SCHEDULE_GEN = schedule_config.get("prompt_schedule_gen", config.PROMPT_SCHEDULE_GEN) - logger.info(f"载入自定义日程prompt:{schedule_config.get('prompt_schedule_gen', config.PROMPT_SCHEDULE_GEN)}") + logger.info( + f"载入自定义日程prompt:{schedule_config.get('prompt_schedule_gen', config.PROMPT_SCHEDULE_GEN)}") def emoji(parent: dict): emoji_config = parent["emoji"] @@ -247,10 +248,13 @@ class BotConfig: config.willing_mode = willing_config.get("willing_mode", config.willing_mode) if config.INNER_VERSION in SpecifierSet(">=0.0.11"): - config.response_willing_amplifier = willing_config.get("response_willing_amplifier", config.response_willing_amplifier) - config.response_interested_rate_amplifier = willing_config.get("response_interested_rate_amplifier", config.response_interested_rate_amplifier) + config.response_willing_amplifier = willing_config.get( + "response_willing_amplifier", config.response_willing_amplifier) + config.response_interested_rate_amplifier = willing_config.get( + "response_interested_rate_amplifier", config.response_interested_rate_amplifier) config.down_frequency_rate = willing_config.get("down_frequency_rate", config.down_frequency_rate) - config.emoji_response_penalty = willing_config.get("emoji_response_penalty", config.emoji_response_penalty) + config.emoji_response_penalty = willing_config.get( + "emoji_response_penalty", config.emoji_response_penalty) def model(parent: dict): # 加载模型配置 @@ -392,9 +396,11 @@ class BotConfig: def response_spliter(parent: dict): response_spliter_config = parent["response_spliter"] - config.enable_response_spliter = response_spliter_config.get("enable_response_spliter", config.enable_response_spliter) + config.enable_response_spliter = response_spliter_config.get( + "enable_response_spliter", config.enable_response_spliter) config.response_max_length = response_spliter_config.get("response_max_length", config.response_max_length) - config.response_max_sentence_num = response_spliter_config.get("response_max_sentence_num", config.response_max_sentence_num) + config.response_max_sentence_num = response_spliter_config.get( + "response_max_sentence_num", config.response_max_sentence_num) def groups(parent: dict): groups_config = parent["groups"] @@ -405,7 +411,8 @@ class BotConfig: def experimental(parent: dict): experimental_config = parent["experimental"] config.enable_friend_chat = experimental_config.get("enable_friend_chat", config.enable_friend_chat) - + config.enable_think_flow = experimental_config.get("enable_think_flow", config.enable_think_flow) + # 版本表达式:>=1.0.0,<2.0.0 # 允许字段:func: method, support: str, notice: str, necessary: bool # 如果使用 notice 字段,在该组配置加载时,会展示该字段对用户的警示 diff --git a/src/plugins/chat/prompt_builder.py b/src/plugins/chat/prompt_builder.py index e6bdaf97..639e9dc0 100644 --- a/src/plugins/chat/prompt_builder.py +++ b/src/plugins/chat/prompt_builder.py @@ -13,7 +13,6 @@ from .relationship_manager import relationship_manager from src.common.logger import get_module_logger from src.think_flow_demo.heartflow import subheartflow_manager -from src.think_flow_demo.outer_world import outer_world logger = get_module_logger("prompt") @@ -58,9 +57,9 @@ class PromptBuilder: mood_prompt = mood_manager.get_prompt() # 日程构建 - current_date = time.strftime("%Y-%m-%d", time.localtime()) - current_time = time.strftime("%H:%M:%S", time.localtime()) - bot_schedule_now_time, bot_schedule_now_activity = bot_schedule.get_current_task() + # current_date = time.strftime("%Y-%m-%d", time.localtime()) + # current_time = time.strftime("%H:%M:%S", time.localtime()) + # bot_schedule_now_time, bot_schedule_now_activity = bot_schedule.get_current_task() # 获取聊天上下文 chat_in_group = True diff --git a/src/plugins/chat/relationship_manager.py b/src/plugins/chat/relationship_manager.py index 53cb0abb..f4cda066 100644 --- a/src/plugins/chat/relationship_manager.py +++ b/src/plugins/chat/relationship_manager.py @@ -122,11 +122,12 @@ class RelationshipManager: relationship.relationship_value = float(relationship.relationship_value.to_decimal()) else: relationship.relationship_value = float(relationship.relationship_value) - logger.info(f"[关系管理] 用户 {user_id}({platform}) 的关系值已转换为double类型: {relationship.relationship_value}") + logger.info( + f"[关系管理] 用户 {user_id}({platform}) 的关系值已转换为double类型: {relationship.relationship_value}") # noqa: E501 except (ValueError, TypeError): # 如果不能解析/强转则将relationship.relationship_value设置为double类型的0 relationship.relationship_value = 0.0 - logger.warning(f"[关系管理] 用户 {user_id}({platform}) 的关系值无法转换为double类型,已设置为0") + logger.warning(f"[关系管理] 用户 {user_id}({platform}) 的无法转换为double类型,已设置为0") relationship.relationship_value += value await self.storage_relationship(relationship) relationship.saved = True diff --git a/src/think_flow_demo/current_mind.py b/src/think_flow_demo/current_mind.py index 2446d66d..78447215 100644 --- a/src/think_flow_demo/current_mind.py +++ b/src/think_flow_demo/current_mind.py @@ -21,7 +21,8 @@ class SubHeartflow: self.current_mind = "" self.past_mind = [] self.current_state : CuttentState = CuttentState() - self.llm_model = LLM_request(model=global_config.llm_sub_heartflow, temperature=0.7, max_tokens=600, request_type="sub_heart_flow") + self.llm_model = LLM_request( + model=global_config.llm_sub_heartflow, temperature=0.7, max_tokens=600, request_type="sub_heart_flow") self.outer_world = None self.main_heartflow_info = "" @@ -52,15 +53,15 @@ class SubHeartflow: related_memory_info = 'memory' message_stream_info = self.outer_world.talking_summary - prompt = f"" + prompt = "" # prompt += f"麦麦的总体想法是:{self.main_heartflow_info}\n\n" prompt += f"{personality_info}\n" prompt += f"现在你正在上网,和qq群里的网友们聊天,群里正在聊的话题是:{message_stream_info}\n" prompt += f"你想起来{related_memory_info}。" prompt += f"刚刚你的想法是{current_thinking_info}。" prompt += f"你现在{mood_info}。" - prompt += f"现在你接下去继续思考,产生新的想法,不要分点输出,输出连贯的内心独白,不要太长,但是记得结合上述的消息,要记得维持住你的人设,关注聊天和新内容,不要思考太多:" - + prompt += "现在你接下去继续思考,产生新的想法,不要分点输出,输出连贯的内心独白,不要太长," + prompt += "但是记得结合上述的消息,要记得维持住你的人设,关注聊天和新内容,不要思考太多:" reponse, reasoning_content = await self.llm_model.generate_response_async(prompt) self.update_current_mind(reponse) @@ -80,7 +81,7 @@ class SubHeartflow: message_new_info = chat_talking_prompt reply_info = reply_content - prompt = f"" + prompt = "" prompt += f"{personality_info}\n" prompt += f"现在你正在上网,和qq群里的网友们聊天,群里正在聊的话题是:{message_stream_info}\n" prompt += f"你想起来{related_memory_info}。" @@ -88,7 +89,8 @@ class SubHeartflow: prompt += f"你现在看到了网友们发的新消息:{message_new_info}\n" prompt += f"你刚刚回复了群友们:{reply_info}" prompt += f"你现在{mood_info}。" - prompt += f"现在你接下去继续思考,产生新的想法,记得保留你刚刚的想法,不要分点输出,输出连贯的内心独白,不要太长,但是记得结合上述的消息,要记得你的人设,关注聊天和新内容,以及你回复的内容,不要思考太多:" + prompt += "现在你接下去继续思考,产生新的想法,记得保留你刚刚的想法,不要分点输出,输出连贯的内心独白" + prompt += "不要太长,但是记得结合上述的消息,要记得你的人设,关注聊天和新内容,以及你回复的内容,不要思考太多:" reponse, reasoning_content = await self.llm_model.generate_response_async(prompt) @@ -103,13 +105,13 @@ class SubHeartflow: current_thinking_info = self.current_mind mood_info = self.current_state.mood # print("麦麦闹情绪了2") - prompt = f"" + prompt = "" prompt += f"{personality_info}\n" - prompt += f"现在你正在上网,和qq群里的网友们聊天" + prompt += "现在你正在上网,和qq群里的网友们聊天" prompt += f"你现在的想法是{current_thinking_info}。" prompt += f"你现在{mood_info}。" - prompt += f"现在请你思考,你想不想发言或者回复,请你输出一个数字,1-10,1表示非常不想,10表示非常想。" - prompt += f"请你用<>包裹你的回复意愿,例如输出<1>表示不想回复,输出<10>表示非常想回复。请你考虑,你完全可以不回复" + prompt += "现在请你思考,你想不想发言或者回复,请你输出一个数字,1-10,1表示非常不想,10表示非常想。" + prompt += "请你用<>包裹你的回复意愿,输出<1>表示不想回复,输出<10>表示非常想回复。请你考虑,你完全可以不回复" response, reasoning_content = await self.llm_model.generate_response_async(prompt) # 解析willing值 diff --git a/src/think_flow_demo/heartflow.py b/src/think_flow_demo/heartflow.py index e455e197..c2e32d60 100644 --- a/src/think_flow_demo/heartflow.py +++ b/src/think_flow_demo/heartflow.py @@ -2,7 +2,6 @@ from .current_mind import SubHeartflow from src.plugins.moods.moods import MoodManager from src.plugins.models.utils_model import LLM_request from src.plugins.chat.config import global_config -from .outer_world import outer_world import asyncio class CuttentState: @@ -21,7 +20,8 @@ class Heartflow: self.current_mind = "你什么也没想" self.past_mind = [] self.current_state : CuttentState = CuttentState() - self.llm_model = LLM_request(model=global_config.llm_heartflow, temperature=0.6, max_tokens=1000, request_type="heart_flow") + self.llm_model = LLM_request( + model=global_config.llm_heartflow, temperature=0.6, max_tokens=1000, request_type="heart_flow") self._subheartflows = {} self.active_subheartflows_nums = 0 @@ -50,7 +50,8 @@ class Heartflow: prompt += f"刚刚你的主要想法是{current_thinking_info}。" prompt += f"你还有一些小想法,因为你在参加不同的群聊天,是你正在做的事情:{sub_flows_info}\n" prompt += f"你现在{mood_info}。" - prompt += f"现在你接下去继续思考,产生新的想法,但是要基于原有的主要想法,不要分点输出,输出连贯的内心独白,不要太长,但是记得结合上述的消息,关注新内容:" + prompt += "现在你接下去继续思考,产生新的想法,但是要基于原有的主要想法,不要分点输出," + prompt += "输出连贯的内心独白,不要太长,但是记得结合上述的消息,关注新内容:" reponse, reasoning_content = await self.llm_model.generate_response_async(prompt) @@ -84,7 +85,8 @@ class Heartflow: prompt += f"现在麦麦的想法是:{self.current_mind}\n" prompt += f"现在麦麦在qq群里进行聊天,聊天的话题如下:{minds_str}\n" prompt += f"你现在{mood_info}\n" - prompt += f"现在请你总结这些聊天内容,注意关注聊天内容对原有的想法的影响,输出连贯的内心独白,不要太长,但是记得结合上述的消息,要记得你的人设,关注新内容:" + prompt += '''现在请你总结这些聊天内容,注意关注聊天内容对原有的想法的影响,输出连贯的内心独白 + 不要太长,但是记得结合上述的消息,要记得你的人设,关注新内容:''' reponse, reasoning_content = await self.llm_model.generate_response_async(prompt) diff --git a/src/think_flow_demo/outer_world.py b/src/think_flow_demo/outer_world.py index 58eb4bbe..c56456bb 100644 --- a/src/think_flow_demo/outer_world.py +++ b/src/think_flow_demo/outer_world.py @@ -3,7 +3,6 @@ import asyncio from datetime import datetime from src.plugins.models.utils_model import LLM_request from src.plugins.chat.config import global_config -import sys from src.common.database import db #存储一段聊天的大致内容 @@ -19,7 +18,8 @@ class Talking_info: self.oberve_interval = 3 - self.llm_summary = LLM_request(model=global_config.llm_outer_world, temperature=0.7, max_tokens=300, request_type="outer_world") + self.llm_summary = LLM_request( + model=global_config.llm_outer_world, temperature=0.7, max_tokens=300, request_type="outer_world") async def start_observe(self): while True: @@ -73,8 +73,9 @@ class Talking_info: prompt = "" prompt = f"你正在参与一个qq群聊的讨论,这个群之前在聊的内容是:{self.talking_summary}\n" prompt += f"现在群里的群友们产生了新的讨论,有了新的发言,具体内容如下:{self.talking_message_str}\n" - prompt += f"以上是群里在进行的聊天,请你对这个聊天内容进行总结,总结内容要包含聊天的大致内容,以及聊天中的一些重要信息,记得不要分点,不要太长,精简的概括成一段文本\n" - prompt += f"总结概括:" + prompt += '''以上是群里在进行的聊天,请你对这个聊天内容进行总结,总结内容要包含聊天的大致内容, + 以及聊天中的一些重要信息,记得不要分点,不要太长,精简的概括成一段文本\n''' + prompt += "总结概括:" self.talking_summary, reasoning_content = await self.llm_summary.generate_response_async(prompt) def translate_message_list_to_str(self): @@ -94,7 +95,8 @@ class OuterWorld: self.outer_world_info = "" self.start_time = int(datetime.now().timestamp()) - self.llm_summary = LLM_request(model=global_config.llm_topic_judge, temperature=0.7, max_tokens=600, request_type="outer_world_info") + self.llm_summary = LLM_request( + model=global_config.llm_outer_world, temperature=0.7, max_tokens=600, request_type="outer_world_info") async def check_and_add_new_observe(self): # 获取所有聊天流 diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 2359b678..e025df46 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -41,7 +41,7 @@ personality_2_probability = 0.2 # 第二种人格出现概率,可以为0 personality_3_probability = 0.1 # 第三种人格出现概率,请确保三个概率相加等于1 [schedule] -enable_schedule_gen = true # 是否启用日程表 +enable_schedule_gen = true # 是否启用日程表(尚未完成) prompt_schedule_gen = "用几句话描述描述性格特点或行动规律,这个特征会用来生成日程表" [message] From 6071317aca77207aa3a2739085188d79cefb836b Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 25 Mar 2025 23:06:14 +0800 Subject: [PATCH 09/18] Update prompt_builder.py --- src/plugins/chat/prompt_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/chat/prompt_builder.py b/src/plugins/chat/prompt_builder.py index 639e9dc0..73f0b0b8 100644 --- a/src/plugins/chat/prompt_builder.py +++ b/src/plugins/chat/prompt_builder.py @@ -166,7 +166,7 @@ class PromptBuilder: 你的网名叫{global_config.BOT_NICKNAME},有人也叫你{"/".join(global_config.BOT_ALIAS_NAMES)},{prompt_personality}。 你正在{chat_target_2},现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些, 尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger} -请回复的平淡一些,简短一些,不要刻意突出自身学科背景, +请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景, 请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。 {moderation_prompt}不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或@等)。""" From f71ce11524d9dd00dbe28ffc9a6b7c9ab6ad39a1 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 25 Mar 2025 23:11:30 +0800 Subject: [PATCH 10/18] Update prompt_builder.py --- src/plugins/chat/prompt_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/chat/prompt_builder.py b/src/plugins/chat/prompt_builder.py index 73f0b0b8..ef070ed2 100644 --- a/src/plugins/chat/prompt_builder.py +++ b/src/plugins/chat/prompt_builder.py @@ -168,7 +168,7 @@ class PromptBuilder: 尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger} 请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景, 请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。 -{moderation_prompt}不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或@等)。""" +{moderation_prompt}不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。""" prompt_check_if_response = "" From 792d65ec1ccc5b7ed93354ddd847cd3c6223fcb8 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 25 Mar 2025 23:47:52 +0800 Subject: [PATCH 11/18] =?UTF-8?q?update=20=E6=9B=B4=E6=96=B0=E6=97=A5?= =?UTF-8?q?=E5=BF=9700.6.0-snapshot-1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 95 ++++++++++++++++++++++++++++++++++++ changelog_config.md | 28 +++++++++-- src/plugins/chat/__init__.py | 5 +- src/plugins/chat/config.py | 7 +++ 4 files changed, 129 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index 6841720b..6c6b2128 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,100 @@ # Changelog AI总结 +## [0.6.0] - 2025-3-25 +### 🌟 核心功能增强 +#### 思维流系统(实验性功能) +- 新增思维流作为实验功能 +- 思维流大核+小核架构 +- 思维流回复意愿模式 + +#### 记忆系统优化 +- 优化记忆抽取策略 +- 优化记忆prompt结构 + +#### 关系系统优化 +- 修复relationship_value类型错误 +- 优化关系管理系统 +- 改进关系值计算方式 + +### 💻 系统架构优化 +#### 配置系统改进 +- 优化配置文件整理 +- 新增分割器功能 +- 新增表情惩罚系数自定义 +- 修复配置文件保存问题 +- 优化配置项管理 +- 新增配置项: + - `schedule`: 日程表生成功能配置 + - `response_spliter`: 回复分割控制 + - `experimental`: 实验性功能开关 + - `llm_outer_world`和`llm_sub_heartflow`: 思维流模型配置 + - `llm_heartflow`: 思维流核心模型配置 + - `prompt_schedule_gen`: 日程生成提示词配置 + - `memory_ban_words`: 记忆过滤词配置 +- 优化配置结构: + - 调整模型配置组织结构 + - 优化配置项默认值 + - 调整配置项顺序 +- 移除冗余配置 + +#### WebUI改进 +- 新增回复意愿模式选择功能 +- 优化WebUI界面 +- 优化WebUI配置保存机制 + +#### 部署支持扩展 +- 优化Docker构建流程 +- 完善Windows脚本支持 +- 优化Linux一键安装脚本 +- 新增macOS教程支持 + +### 🐛 问题修复 +#### 功能稳定性 +- 修复表情包审查器问题 +- 修复心跳发送问题 +- 修复拍一拍消息处理异常 +- 修复日程报错问题 +- 修复文件读写编码问题 +- 修复西文字符分割问题 +- 修复自定义API提供商识别问题 +- 修复人格设置保存问题 +- 修复EULA和隐私政策编码问题 +- 修复cfg变量引用问题 + +#### 性能优化 +- 提高topic提取效率 +- 优化logger输出格式 +- 优化cmd清理功能 +- 改进LLM使用统计 +- 优化记忆处理效率 + +### 📚 文档更新 +- 更新README.md内容 +- 添加macOS部署教程 +- 优化文档结构 +- 更新EULA和隐私政策 +- 完善部署文档 + +### 🔧 其他改进 +- 新增神秘小测验功能 +- 新增人格测评模型 +- 优化表情包审查功能 +- 改进消息转发处理 +- 优化代码风格和格式 +- 完善异常处理机制 +- 优化日志输出格式 + +### 主要改进方向 +1. 完善思维流系统功能 +2. 优化记忆系统效率 +3. 改进关系系统稳定性 +4. 提升配置系统可用性 +5. 加强WebUI功能 +6. 完善部署文档 + + + ## [0.5.15] - 2025-3-17 ### 🌟 核心功能增强 #### 关系系统升级 @@ -213,3 +307,4 @@ AI总结 + diff --git a/changelog_config.md b/changelog_config.md index c4c56064..92a522a2 100644 --- a/changelog_config.md +++ b/changelog_config.md @@ -1,12 +1,32 @@ # Changelog +## [0.0.11] - 2025-3-12 +### Added +- 新增了 `schedule` 配置项,用于配置日程表生成功能 +- 新增了 `response_spliter` 配置项,用于控制回复分割 +- 新增了 `experimental` 配置项,用于实验性功能开关 +- 新增了 `llm_outer_world` 和 `llm_sub_heartflow` 模型配置 +- 新增了 `llm_heartflow` 模型配置 +- 在 `personality` 配置项中新增了 `prompt_schedule_gen` 参数 + +### Changed +- 优化了模型配置的组织结构 +- 调整了部分配置项的默认值 +- 调整了配置项的顺序,将 `groups` 配置项移到了更靠前的位置 +- 在 `message` 配置项中: + - 新增了 `max_response_length` 参数 +- 在 `willing` 配置项中新增了 `emoji_response_penalty` 参数 +- 将 `personality` 配置项中的 `prompt_schedule` 重命名为 `prompt_schedule_gen` + +### Removed +- 移除了 `min_text_length` 配置项 +- 移除了 `cq_code` 配置项 +- 移除了 `others` 配置项(其功能已整合到 `experimental` 中) + ## [0.0.5] - 2025-3-11 ### Added - 新增了 `alias_names` 配置项,用于指定麦麦的别名。 ## [0.0.4] - 2025-3-9 ### Added -- 新增了 `memory_ban_words` 配置项,用于指定不希望记忆的词汇。 - - - +- 新增了 `memory_ban_words` 配置项,用于指定不希望记忆的词汇。 \ No newline at end of file diff --git a/src/plugins/chat/__init__.py b/src/plugins/chat/__init__.py index d8a41fe8..39f3ddfb 100644 --- a/src/plugins/chat/__init__.py +++ b/src/plugins/chat/__init__.py @@ -36,8 +36,9 @@ config = driver.config # 初始化表情管理器 emoji_manager.initialize() - -logger.debug(f"正在唤醒{global_config.BOT_NICKNAME}......") +logger.success("--------------------------------") +logger.success(f"正在唤醒{global_config.BOT_NICKNAME}......使用版本:{global_config.MAI_VERSION}") +logger.success("--------------------------------") # 注册消息处理器 msg_in = on_message(priority=5) # 注册和bot相关的通知处理器 diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index 2b73996f..2d9badbc 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -196,6 +196,12 @@ class BotConfig: def load_config(cls, config_path: str = None) -> "BotConfig": """从TOML配置文件加载配置""" config = cls() + + def mai_version(parent: dict): + mai_version_config = parent["mai_version"] + version = mai_version_config.get("version") + version_fix = mai_version_config.get("version-fix") + config.MAI_VERSION = f"{version}-{version_fix}" def personality(parent: dict): personality_config = parent["personality"] @@ -420,6 +426,7 @@ class BotConfig: # 正常执行程序,但是会看到这条自定义提示 include_configs = { "bot": {"func": bot, "support": ">=0.0.0"}, + "mai_version": {"func": mai_version, "support": ">=0.0.11"}, "groups": {"func": groups, "support": ">=0.0.0"}, "personality": {"func": personality, "support": ">=0.0.0"}, "schedule": {"func": schedule, "support": ">=0.0.11", "necessary": False}, From 1b960b32b4c87ad5886ed1dbcdd8182d1a73b242 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 26 Mar 2025 00:03:33 +0800 Subject: [PATCH 12/18] Update __init__.py --- src/plugins/chat/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/chat/__init__.py b/src/plugins/chat/__init__.py index 39f3ddfb..78e026ca 100644 --- a/src/plugins/chat/__init__.py +++ b/src/plugins/chat/__init__.py @@ -18,7 +18,6 @@ from ..memory_system.memory import hippocampus from .message_sender import message_manager, message_sender from .storage import MessageStorage from src.common.logger import get_module_logger -# from src.think_flow_demo.current_mind import subheartflow from src.think_flow_demo.outer_world import outer_world from src.think_flow_demo.heartflow import subheartflow_manager From 681e1aa0fcd72431c6b3ed04397002ea3456b63b Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 26 Mar 2025 00:16:56 +0800 Subject: [PATCH 13/18] Merge remote-tracking branch 'origin/main-fix' into think_flow_test --- MaiLauncher.bat | 14 ++ bot.py | 2 - docs/doc1.md | 203 ++++++++++++++++++-------- docs/docker_deploy.md | 2 +- src/plugins/chat/llm_generator.py | 2 +- src/plugins/willing/mode_classical.py | 3 +- 6 files changed, 160 insertions(+), 66 deletions(-) diff --git a/MaiLauncher.bat b/MaiLauncher.bat index 619f9c65..03e59b59 100644 --- a/MaiLauncher.bat +++ b/MaiLauncher.bat @@ -277,6 +277,19 @@ if defined VIRTUAL_ENV ( goto menu ) +if exist "%_root%\config\conda_env" ( + set /p CONDA_ENV=<"%_root%\config\conda_env" + call conda activate !CONDA_ENV! || ( + echo 激活失败,可能原因: + echo 1. 环境不存在 + echo 2. conda配置异常 + pause + goto conda_menu + ) + echo 成功激活conda环境:!CONDA_ENV! + goto menu +) + echo ===================================== echo 虚拟环境检测警告: echo 当前使用系统Python路径:!PYTHON_HOME! @@ -390,6 +403,7 @@ call conda activate !CONDA_ENV! || ( goto conda_menu ) echo 成功激活conda环境:!CONDA_ENV! +echo !CONDA_ENV! > "%_root%\config\conda_env" echo 要安装依赖吗? set /p install_confirm="继续?(Y/N): " if /i "!install_confirm!"=="Y" ( diff --git a/bot.py b/bot.py index 4f649ed9..30714e84 100644 --- a/bot.py +++ b/bot.py @@ -139,12 +139,10 @@ async def graceful_shutdown(): uvicorn_server.force_exit = True # 强制退出 await uvicorn_server.shutdown() - logger.info("正在关闭所有任务...") tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] for task in tasks: task.cancel() await asyncio.gather(*tasks, return_exceptions=True) - logger.info("所有任务已关闭") except Exception as e: logger.error(f"麦麦关闭失败: {e}") diff --git a/docs/doc1.md b/docs/doc1.md index 79ef7812..e8aa0f0d 100644 --- a/docs/doc1.md +++ b/docs/doc1.md @@ -5,88 +5,171 @@ - **README.md**: 项目的概述和使用说明。 - **requirements.txt**: 项目所需的Python依赖包列表。 - **bot.py**: 主启动文件,负责环境配置加载和NoneBot初始化。 -- **webui.py**: Web界面实现,提供图形化操作界面。 - **template.env**: 环境变量模板文件。 - **pyproject.toml**: Python项目配置文件。 - **docker-compose.yml** 和 **Dockerfile**: Docker配置文件,用于容器化部署。 -- **run_*.bat**: 各种启动脚本,包括开发环境、WebUI和记忆可视化等功能。 -- **EULA.md** 和 **PRIVACY.md**: 用户协议和隐私政策文件。 -- **changelog.md**: 版本更新日志。 +- **run_*.bat**: 各种启动脚本,包括数据库、maimai和thinking功能。 ## `src/` 目录结构 - **`plugins/` 目录**: 存放不同功能模块的插件。 - - **chat/**: 处理聊天相关的功能。 - - **memory_system/**: 处理机器人的记忆系统。 - - **personality/**: 处理机器人的性格系统。 - - **willing/**: 管理机器人的意愿系统。 + - **chat/**: 处理聊天相关的功能,如消息发送和接收。 + - **memory_system/**: 处理机器人的记忆功能。 + - **knowledege/**: 知识库相关功能。 - **models/**: 模型相关工具。 - - **schedule/**: 处理日程管理功能。 - - **moods/**: 情绪管理系统。 - - **zhishi/**: 知识库相关功能。 - - **remote/**: 远程控制功能。 - - **utils/**: 通用工具函数。 - - **config_reload/**: 配置热重载功能。 + - **schedule/**: 处理日程管理的功能。 - **`gui/` 目录**: 存放图形用户界面相关的代码。 + - **reasoning_gui.py**: 负责推理界面的实现,提供用户交互。 - **`common/` 目录**: 存放通用的工具和库。 + - **database.py**: 处理与数据库的交互,负责数据的存储和检索。 + - ****init**.py**: 初始化模块。 -- **`think_flow_demo/` 目录**: 思维流程演示相关代码。 +## `config/` 目录 -## 新增特色功能 +- **bot_config_template.toml**: 机器人配置模板。 +- **auto_format.py**: 自动格式化工具。 -1. **WebUI系统**: - - 提供图形化操作界面 - - 支持实时监控和控制 - - 可视化配置管理 +### `src/plugins/chat/` 目录文件详细介绍 -2. **多模式启动支持**: - - 开发环境(run_dev.bat) - - 生产环境 - - WebUI模式(webui_conda.bat) - - 记忆可视化(run_memory_vis.bat) +1. **`__init__.py`**: + - 初始化 `chat` 模块,使其可以作为一个包被导入。 -3. **增强的情感系统**: - - 情绪管理(moods插件) - - 性格系统(personality插件) - - 意愿系统(willing插件) +2. **`bot.py`**: + - 主要的聊天机器人逻辑实现,处理消息的接收、思考和回复。 + - 包含 `ChatBot` 类,负责消息处理流程控制。 + - 集成记忆系统和意愿管理。 -4. **远程控制功能**: - - 支持远程操作和监控 - - 分布式部署支持 +3. **`config.py`**: + - 配置文件,定义了聊天机器人的各种参数和设置。 + - 包含 `BotConfig` 和全局配置对象 `global_config`。 -5. **配置管理**: - - 支持配置热重载 - - 多环境配置(dev/prod) - - 自动配置更新检查 +4. **`cq_code.py`**: + - 处理 CQ 码(CoolQ 码),用于发送和接收特定格式的消息。 -6. **安全和隐私**: - - 用户协议(EULA)支持 - - 隐私政策遵守 - - 敏感信息保护 +5. **`emoji_manager.py`**: + - 管理表情包的发送和接收,根据情感选择合适的表情。 + - 提供根据情绪获取表情的方法。 -## 系统架构特点 +6. **`llm_generator.py`**: + - 生成基于大语言模型的回复,处理用户输入并生成相应的文本。 + - 通过 `ResponseGenerator` 类实现回复生成。 -1. **模块化设计**: - - 插件系统支持动态加载 - - 功能模块独立封装 - - 高度可扩展性 +7. **`message.py`**: + - 定义消息的结构和处理逻辑,包含多种消息类型: + - `Message`: 基础消息类 + - `MessageSet`: 消息集合 + - `Message_Sending`: 发送中的消息 + - `Message_Thinking`: 思考状态的消息 -2. **多层次AI交互**: - - 记忆系统 - - 情感系统 - - 知识库集成 - - 意愿管理 +8. **`message_sender.py`**: + - 控制消息的发送逻辑,确保消息按照特定规则发送。 + - 包含 `message_manager` 对象,用于管理消息队列。 -3. **完善的开发支持**: - - 开发环境配置 - - 代码规范检查 - - 自动化部署 - - Docker支持 +9. **`prompt_builder.py`**: + - 构建用于生成回复的提示,优化机器人的响应质量。 -4. **用户友好**: - - 图形化界面 - - 多种启动方式 - - 配置自动化 - - 详细的文档支持 +10. **`relationship_manager.py`**: + - 管理用户之间的关系,记录用户的互动和偏好。 + - 提供更新关系和关系值的方法。 + +11. **`Segment_builder.py`**: + - 构建消息片段的工具。 + +12. **`storage.py`**: + - 处理数据存储,负责将聊天记录和用户信息保存到数据库。 + - 实现 `MessageStorage` 类管理消息存储。 + +13. **`thinking_idea.py`**: + - 实现机器人的思考机制。 + +14. **`topic_identifier.py`**: + - 识别消息中的主题,帮助机器人理解用户的意图。 + +15. **`utils.py`** 和 **`utils_*.py`** 系列文件: + - 存放各种工具函数,提供辅助功能以支持其他模块。 + - 包括 `utils_cq.py`、`utils_image.py`、`utils_user.py` 等专门工具。 + +16. **`willing_manager.py`**: + - 管理机器人的回复意愿,动态调整回复概率。 + - 通过多种因素(如被提及、话题兴趣度)影响回复决策。 + +### `src/plugins/memory_system/` 目录文件介绍 + +1. **`memory.py`**: + - 实现记忆管理核心功能,包含 `memory_graph` 对象。 + - 提供相关项目检索,支持多层次记忆关联。 + +2. **`draw_memory.py`**: + - 记忆可视化工具。 + +3. **`memory_manual_build.py`**: + - 手动构建记忆的工具。 + +4. **`offline_llm.py`**: + - 离线大语言模型处理功能。 + +## 消息处理流程 + +### 1. 消息接收与预处理 + +- 通过 `ChatBot.handle_message()` 接收群消息。 +- 进行用户和群组的权限检查。 +- 更新用户关系信息。 +- 创建标准化的 `Message` 对象。 +- 对消息进行过滤和敏感词检测。 + +### 2. 主题识别与决策 + +- 使用 `topic_identifier` 识别消息主题。 +- 通过记忆系统检查对主题的兴趣度。 +- `willing_manager` 动态计算回复概率。 +- 根据概率决定是否回复消息。 + +### 3. 回复生成与发送 + +- 如需回复,首先创建 `Message_Thinking` 对象表示思考状态。 +- 调用 `ResponseGenerator.generate_response()` 生成回复内容和情感状态。 +- 删除思考消息,创建 `MessageSet` 准备发送回复。 +- 计算模拟打字时间,设置消息发送时间点。 +- 可能附加情感相关的表情包。 +- 通过 `message_manager` 将消息加入发送队列。 + +### 消息发送控制系统 + +`message_sender.py` 中实现了消息发送控制系统,采用三层结构: + +1. **消息管理**: + - 支持单条消息和消息集合的发送。 + - 处理思考状态消息,控制思考时间。 + - 模拟人类打字速度,添加自然发送延迟。 + +2. **情感表达**: + - 根据生成回复的情感状态选择匹配的表情包。 + - 通过 `emoji_manager` 管理表情资源。 + +3. **记忆交互**: + - 通过 `memory_graph` 检索相关记忆。 + - 根据记忆内容影响回复意愿和内容。 + +## 系统特色功能 + +1. **智能回复意愿系统**: + - 动态调整回复概率,模拟真实人类交流特性。 + - 考虑多种因素:被提及、话题兴趣度、用户关系等。 + +2. **记忆系统集成**: + - 支持多层次记忆关联和检索。 + - 影响机器人的兴趣和回复内容。 + +3. **自然交流模拟**: + - 模拟思考和打字过程,添加合理延迟。 + - 情感表达与表情包结合。 + +4. **多环境配置支持**: + - 支持开发环境和生产环境的不同配置。 + - 通过环境变量和配置文件灵活管理设置。 + +5. **Docker部署支持**: + - 提供容器化部署方案,简化安装和运行。 diff --git a/docs/docker_deploy.md b/docs/docker_deploy.md index f78f73dc..38eb5444 100644 --- a/docs/docker_deploy.md +++ b/docs/docker_deploy.md @@ -41,7 +41,7 @@ NAPCAT_UID=$(id -u) NAPCAT_GID=$(id -g) docker-compose up -d ### 3. 修改配置并重启Docker -- 请前往 [🎀 新手配置指南](docs/installation_cute.md) 或 [⚙️ 标准配置指南](docs/installation_standard.md) 完成`.env.prod`与`bot_config.toml`配置文件的编写\ +- 请前往 [🎀 新手配置指南](./installation_cute.md) 或 [⚙️ 标准配置指南](./installation_standard.md) 完成`.env.prod`与`bot_config.toml`配置文件的编写\ **需要注意`.env.prod`中HOST处IP的填写,Docker中部署和系统中直接安装的配置会有所不同** - 重启Docker容器: diff --git a/src/plugins/chat/llm_generator.py b/src/plugins/chat/llm_generator.py index b9decdaa..316260c8 100644 --- a/src/plugins/chat/llm_generator.py +++ b/src/plugins/chat/llm_generator.py @@ -35,7 +35,7 @@ class ResponseGenerator: request_type="response", ) self.model_v3 = LLM_request( - model=global_config.llm_normal, temperature=0.9, max_tokens=3000, request_type="response" + model=global_config.llm_normal, temperature=0.7, max_tokens=3000, request_type="response" ) self.model_r1_distill = LLM_request( model=global_config.llm_reasoning_minor, temperature=0.7, max_tokens=3000, request_type="response" diff --git a/src/plugins/willing/mode_classical.py b/src/plugins/willing/mode_classical.py index a0ec90ff..155b2ba7 100644 --- a/src/plugins/willing/mode_classical.py +++ b/src/plugins/willing/mode_classical.py @@ -42,10 +42,9 @@ class WillingManager: interested_rate = interested_rate * config.response_interested_rate_amplifier - if interested_rate > 0.4: current_willing += interested_rate - 0.3 - + if is_mentioned_bot and current_willing < 1.0: current_willing += 1 elif is_mentioned_bot: From 88e160eb55d2b76c21fb8d5fb15129f701f9724d Mon Sep 17 00:00:00 2001 From: Noble Fish <89088785+DeathFishAtEase@users.noreply.github.com> Date: Wed, 26 Mar 2025 12:14:47 +0800 Subject: [PATCH 14/18] Update manual_deploy_windows.md --- docs/manual_deploy_windows.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/manual_deploy_windows.md b/docs/manual_deploy_windows.md index 37f0a5e3..d5115120 100644 --- a/docs/manual_deploy_windows.md +++ b/docs/manual_deploy_windows.md @@ -75,22 +75,22 @@ conda activate maimbot pip install -r requirements.txt ``` -### 2️⃣ **然后你需要启动MongoDB数据库,来存储信息** +### 3️⃣ **然后你需要启动MongoDB数据库,来存储信息** - 安装并启动MongoDB服务 - 默认连接本地27017端口 -### 3️⃣ **配置NapCat,让麦麦bot与qq取得联系** +### 4️⃣ **配置NapCat,让麦麦bot与qq取得联系** - 安装并登录NapCat(用你的qq小号) - 添加反向WS: `ws://127.0.0.1:8080/onebot/v11/ws` -### 4️⃣ **配置文件设置,让麦麦Bot正常工作** +### 5️⃣ **配置文件设置,让麦麦Bot正常工作** - 修改环境配置文件:`.env.prod` - 修改机器人配置文件:`bot_config.toml` -### 5️⃣ **启动麦麦机器人** +### 6️⃣ **启动麦麦机器人** - 打开命令行,cd到对应路径 @@ -104,7 +104,7 @@ nb run python bot.py ``` -### 6️⃣ **其他组件(可选)** +### 7️⃣ **其他组件(可选)** - `run_thingking.bat`: 启动可视化推理界面(未完善) - 直接运行 knowledge.py生成知识库 From 3c4f492b76d9a42eb6c637290bccbd6abead86ff Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 26 Mar 2025 12:51:39 +0800 Subject: [PATCH 15/18] =?UTF-8?q?fix=20=E6=80=9D=E7=BB=B4=E6=B5=81?= =?UTF-8?q?=E4=B8=8D=E4=BC=9A=E8=87=AA=E5=8A=A8=E5=81=9C=E6=AD=A2=E6=B6=88?= =?UTF-8?q?=E8=80=97token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/think_flow_demo/current_mind.py | 17 +++++++++++++---- src/think_flow_demo/heartflow.py | 4 ++-- template/bot_config_template.toml | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/think_flow_demo/current_mind.py b/src/think_flow_demo/current_mind.py index 78447215..6facdbf9 100644 --- a/src/think_flow_demo/current_mind.py +++ b/src/think_flow_demo/current_mind.py @@ -4,6 +4,7 @@ from src.plugins.moods.moods import MoodManager from src.plugins.models.utils_model import LLM_request from src.plugins.chat.config import global_config import re +import time class CuttentState: def __init__(self): self.willing = 0 @@ -29,6 +30,8 @@ class SubHeartflow: self.observe_chat_id = None + self.last_reply_time = time.time() + if not self.current_mind: self.current_mind = "你什么也没想" @@ -38,10 +41,14 @@ class SubHeartflow: async def subheartflow_start_working(self): while True: - await self.do_a_thinking() - print("麦麦闹情绪了") - await self.judge_willing() - await asyncio.sleep(30) + current_time = time.time() + if current_time - self.last_reply_time > 180: # 3分钟 = 180秒 + # print(f"{self.observe_chat_id}麦麦已经3分钟没有回复了,暂时停止思考") + await asyncio.sleep(25) # 每30秒检查一次 + else: + await self.do_a_thinking() + await self.judge_willing() + await asyncio.sleep(25) async def do_a_thinking(self): print("麦麦小脑袋转起来了") @@ -99,6 +106,8 @@ class SubHeartflow: self.current_mind = reponse print(f"{self.observe_chat_id}麦麦的脑内状态:{self.current_mind}") + self.last_reply_time = time.time() + async def judge_willing(self): # print("麦麦闹情绪了1") personality_info = open("src/think_flow_demo/personality_info.txt", "r", encoding="utf-8").read() diff --git a/src/think_flow_demo/heartflow.py b/src/think_flow_demo/heartflow.py index c2e32d60..45843e49 100644 --- a/src/think_flow_demo/heartflow.py +++ b/src/think_flow_demo/heartflow.py @@ -30,7 +30,7 @@ class Heartflow: async def heartflow_start_working(self): while True: - await self.do_a_thinking() + # await self.do_a_thinking() await asyncio.sleep(60) async def do_a_thinking(self): @@ -82,7 +82,7 @@ class Heartflow: prompt = "" prompt += f"{personality_info}\n" - prompt += f"现在麦麦的想法是:{self.current_mind}\n" + prompt += f"现在{global_config.BOT_NICKNAME}的想法是:{self.current_mind}\n" prompt += f"现在麦麦在qq群里进行聊天,聊天的话题如下:{minds_str}\n" prompt += f"你现在{mood_info}\n" prompt += '''现在请你总结这些聊天内容,注意关注聊天内容对原有的想法的影响,输出连贯的内心独白 diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index e025df46..6591d427 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -132,7 +132,7 @@ enable = true [experimental] enable_friend_chat = false # 是否启用好友聊天 -enable_think_flow = false # 是否启用思维流 +enable_think_flow = false # 是否启用思维流 注意:可能会消耗大量token,请谨慎开启 #下面的模型若使用硅基流动则不需要更改,使用ds官方则改成.env.prod自定义的宏,使用自定义模型则选择定位相似的模型自己填写 #推理模型 From 07d891a9d79c91b96f647a92a4ffd7e8b3f63349 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 26 Mar 2025 13:37:49 +0800 Subject: [PATCH 16/18] Merge pull request #570 from Tianmoy/main-fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix:修复docs跳转错误 --- src/common/logger.py | 21 ++++++++++++++++++++- src/plugins/chat/__init__.py | 2 +- src/plugins/moods/moods.py | 9 +++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/common/logger.py b/src/common/logger.py index 91f1a1da..45d6f415 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -86,6 +86,25 @@ MEMORY_STYLE_CONFIG = { }, } + +#MOOD +MOOD_STYLE_CONFIG = { + "advanced": { + "console_format": ( + "{time:YYYY-MM-DD HH:mm:ss} | " + "{level: <8} | " + "{extra[module]: <12} | " + "心情 | " + "{message}" + ), + "file_format": ("{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 心情 | {message}"), + }, + "simple": { + "console_format": ("{time:MM-DD HH:mm} | 心情 | {message}"), + "file_format": ("{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 心情 | {message}"), + }, +} + SENDER_STYLE_CONFIG = { "advanced": { "console_format": ( @@ -163,7 +182,7 @@ TOPIC_STYLE_CONFIG = TOPIC_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else TOPIC_ST SENDER_STYLE_CONFIG = SENDER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else SENDER_STYLE_CONFIG["advanced"] LLM_STYLE_CONFIG = LLM_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else LLM_STYLE_CONFIG["advanced"] CHAT_STYLE_CONFIG = CHAT_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else CHAT_STYLE_CONFIG["advanced"] - +MOOD_STYLE_CONFIG = MOOD_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else MOOD_STYLE_CONFIG["advanced"] def is_registered_module(record: dict) -> bool: """检查是否为已注册的模块""" diff --git a/src/plugins/chat/__init__.py b/src/plugins/chat/__init__.py index 78e026ca..f51184a7 100644 --- a/src/plugins/chat/__init__.py +++ b/src/plugins/chat/__init__.py @@ -150,7 +150,7 @@ async def merge_memory_task(): # print("\033[1;32m[记忆整合]\033[0m 记忆整合完成") -@scheduler.scheduled_job("interval", seconds=30, id="print_mood") +@scheduler.scheduled_job("interval", seconds=15, id="print_mood") async def print_mood_task(): """每30秒打印一次情绪状态""" mood_manager = MoodManager.get_instance() diff --git a/src/plugins/moods/moods.py b/src/plugins/moods/moods.py index 3e977d02..986075da 100644 --- a/src/plugins/moods/moods.py +++ b/src/plugins/moods/moods.py @@ -4,9 +4,14 @@ import time from dataclasses import dataclass from ..chat.config import global_config -from src.common.logger import get_module_logger +from src.common.logger import get_module_logger, LogConfig, MOOD_STYLE_CONFIG -logger = get_module_logger("mood_manager") +mood_config = LogConfig( + # 使用海马体专用样式 + console_format=MOOD_STYLE_CONFIG["console_format"], + file_format=MOOD_STYLE_CONFIG["file_format"], +) +logger = get_module_logger("mood_manager", config=mood_config) @dataclass From f1003030c702aeb965170c2656ed832b4bc497b0 Mon Sep 17 00:00:00 2001 From: HYY1116 Date: Wed, 26 Mar 2025 15:01:25 +0800 Subject: [PATCH 17/18] =?UTF-8?q?feat:=20=E5=9C=A8=E7=BA=BF=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=A2=9E=E5=8A=A0=E7=89=88=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/remote/remote.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/plugins/remote/remote.py b/src/plugins/remote/remote.py index fdc805df..8586aa67 100644 --- a/src/plugins/remote/remote.py +++ b/src/plugins/remote/remote.py @@ -54,7 +54,9 @@ def send_heartbeat(server_url, client_id): sys = platform.system() try: headers = {"Client-ID": client_id, "User-Agent": f"HeartbeatClient/{client_id[:8]}"} - data = json.dumps({"system": sys}) + data = json.dumps( + {"system": sys, "Version": global_config.MAI_VERSION}, + ) response = requests.post(f"{server_url}/api/clients", headers=headers, data=data) if response.status_code == 201: @@ -92,9 +94,9 @@ class HeartbeatThread(threading.Thread): logger.info(f"{self.interval}秒后发送下一次心跳...") else: logger.info(f"{self.interval}秒后重试...") - + self.last_heartbeat_time = time.time() - + # 使用可中断的等待代替 sleep # 每秒检查一次是否应该停止或发送心跳 remaining_wait = self.interval @@ -104,7 +106,7 @@ class HeartbeatThread(threading.Thread): if self.stop_event.wait(wait_time): break # 如果事件被设置,立即退出等待 remaining_wait -= wait_time - + # 检查是否由于外部原因导致间隔异常延长 if time.time() - self.last_heartbeat_time >= self.interval * 1.5: logger.warning("检测到心跳间隔异常延长,立即发送心跳") From 6a805b7b22c636781f702b659fa611e7d9e1e7b0 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 26 Mar 2025 15:04:04 +0800 Subject: [PATCH 18/18] =?UTF-8?q?better=20=E6=96=B0=E6=97=A5=E7=A8=8B?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/schedule_generator copy.py | 191 --------------- .../schedule/schedule_generator_pro.py | 222 ++++++++++++++++++ 2 files changed, 222 insertions(+), 191 deletions(-) delete mode 100644 src/plugins/schedule/schedule_generator copy.py create mode 100644 src/plugins/schedule/schedule_generator_pro.py diff --git a/src/plugins/schedule/schedule_generator copy.py b/src/plugins/schedule/schedule_generator copy.py deleted file mode 100644 index eff0a08d..00000000 --- a/src/plugins/schedule/schedule_generator copy.py +++ /dev/null @@ -1,191 +0,0 @@ -import datetime -import json -import re -import os -import sys -from typing import Dict, Union - - -# 添加项目根目录到 Python 路径 -root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) -sys.path.append(root_path) - -from src.common.database import db # noqa: E402 -from src.common.logger import get_module_logger # noqa: E402 -from src.plugins.schedule.offline_llm import LLMModel # noqa: E402 -from src.plugins.chat.config import global_config # noqa: E402 - -logger = get_module_logger("scheduler") - - -class ScheduleGenerator: - enable_output: bool = True - - def __init__(self): - # 使用离线LLM模型 - self.llm_scheduler = LLMModel(model_name="Pro/deepseek-ai/DeepSeek-V3", temperature=0.9) - self.today_schedule_text = "" - self.today_schedule = {} - self.tomorrow_schedule_text = "" - self.tomorrow_schedule = {} - self.yesterday_schedule_text = "" - self.yesterday_schedule = {} - - async def initialize(self): - today = datetime.datetime.now() - tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) - yesterday = datetime.datetime.now() - datetime.timedelta(days=1) - - self.today_schedule_text, self.today_schedule = await self.generate_daily_schedule(target_date=today) - self.tomorrow_schedule_text, self.tomorrow_schedule = await self.generate_daily_schedule( - target_date=tomorrow, read_only=True - ) - self.yesterday_schedule_text, self.yesterday_schedule = await self.generate_daily_schedule( - target_date=yesterday, read_only=True - ) - - async def generate_daily_schedule( - self, target_date: datetime.datetime = None, read_only: bool = False - ) -> Dict[str, str]: - date_str = target_date.strftime("%Y-%m-%d") - weekday = target_date.strftime("%A") - - schedule_text = str - - existing_schedule = db.schedule.find_one({"date": date_str}) - if existing_schedule: - if self.enable_output: - logger.debug(f"{date_str}的日程已存在:") - schedule_text = existing_schedule["schedule"] - # print(self.schedule_text) - - elif not read_only: - logger.debug(f"{date_str}的日程不存在,准备生成新的日程。") - prompt = ( - f"""我是{global_config.BOT_NICKNAME},{global_config.PROMPT_SCHEDULE_GEN},请为我生成{date_str}({weekday})的日程安排,包括:""" - + """ - 1. 早上的学习和工作安排 - 2. 下午的活动和任务 - 3. 晚上的计划和休息时间 - 请按照时间顺序列出具体时间点和对应的活动,用一个时间点而不是时间段来表示时间,用JSON格式返回日程表, - 仅返回内容,不要返回注释,不要添加任何markdown或代码块样式,时间采用24小时制, - 格式为{"时间": "活动","时间": "活动",...}。""" - ) - - try: - schedule_text, _ = self.llm_scheduler.generate_response(prompt) - db.schedule.insert_one({"date": date_str, "schedule": schedule_text}) - self.enable_output = True - except Exception as e: - logger.error(f"生成日程失败: {str(e)}") - schedule_text = "生成日程时出错了" - # print(self.schedule_text) - else: - if self.enable_output: - logger.debug(f"{date_str}的日程不存在。") - schedule_text = "忘了" - - return schedule_text, None - - schedule_form = self._parse_schedule(schedule_text) - return schedule_text, schedule_form - - def _parse_schedule(self, schedule_text: str) -> Union[bool, Dict[str, str]]: - """解析日程文本,转换为时间和活动的字典""" - try: - reg = r"\{(.|\r|\n)+\}" - matched = re.search(reg, schedule_text)[0] - schedule_dict = json.loads(matched) - return schedule_dict - except json.JSONDecodeError: - logger.exception("解析日程失败: {}".format(schedule_text)) - return False - - def _parse_time(self, time_str: str) -> str: - """解析时间字符串,转换为时间""" - return datetime.datetime.strptime(time_str, "%H:%M") - - def get_current_task(self) -> str: - """获取当前时间应该进行的任务""" - current_time = datetime.datetime.now().strftime("%H:%M") - - # 找到最接近当前时间的任务 - closest_time = None - min_diff = float("inf") - - # 检查今天的日程 - if not self.today_schedule: - return "摸鱼" - for time_str in self.today_schedule.keys(): - diff = abs(self._time_diff(current_time, time_str)) - if closest_time is None or diff < min_diff: - closest_time = time_str - min_diff = diff - - # 检查昨天的日程中的晚间任务 - if self.yesterday_schedule: - for time_str in self.yesterday_schedule.keys(): - if time_str >= "20:00": # 只考虑晚上8点之后的任务 - # 计算与昨天这个时间点的差异(需要加24小时) - diff = abs(self._time_diff(current_time, time_str)) - if diff < min_diff: - closest_time = time_str - min_diff = diff - return closest_time, self.yesterday_schedule[closest_time] - - if closest_time: - return closest_time, self.today_schedule[closest_time] - return "摸鱼" - - def _time_diff(self, time1: str, time2: str) -> int: - """计算两个时间字符串之间的分钟差""" - if time1 == "24:00": - time1 = "23:59" - if time2 == "24:00": - time2 = "23:59" - t1 = datetime.datetime.strptime(time1, "%H:%M") - t2 = datetime.datetime.strptime(time2, "%H:%M") - diff = int((t2 - t1).total_seconds() / 60) - # 考虑时间的循环性 - if diff < -720: - diff += 1440 # 加一天的分钟 - elif diff > 720: - diff -= 1440 # 减一天的分钟 - # print(f"时间1[{time1}]: 时间2[{time2}],差值[{diff}]分钟") - return diff - - def print_schedule(self): - """打印完整的日程安排""" - if not self._parse_schedule(self.today_schedule_text): - logger.warning("今日日程有误,将在下次运行时重新生成") - db.schedule.delete_one({"date": datetime.datetime.now().strftime("%Y-%m-%d")}) - else: - logger.info("=== 今日日程安排 ===") - for time_str, activity in self.today_schedule.items(): - logger.info(f"时间[{time_str}]: 活动[{activity}]") - logger.info("==================") - self.enable_output = False - - -async def main(): - # 使用示例 - scheduler = ScheduleGenerator() - await scheduler.initialize() - scheduler.print_schedule() - print("\n当前任务:") - print(await scheduler.get_current_task()) - - print("昨天日程:") - print(scheduler.yesterday_schedule) - print("今天日程:") - print(scheduler.today_schedule) - print("明天日程:") - print(scheduler.tomorrow_schedule) - -# 当作为组件导入时使用的实例 -bot_schedule = ScheduleGenerator() - -if __name__ == "__main__": - import asyncio - # 当直接运行此文件时执行 - asyncio.run(main()) diff --git a/src/plugins/schedule/schedule_generator_pro.py b/src/plugins/schedule/schedule_generator_pro.py new file mode 100644 index 00000000..5a2c2a68 --- /dev/null +++ b/src/plugins/schedule/schedule_generator_pro.py @@ -0,0 +1,222 @@ +import datetime +import json +import re +import os +import sys +from typing import Dict, Union +# 添加项目根目录到 Python 路径 +root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) +sys.path.append(root_path) + +from src.common.database import db # noqa: E402 +from src.common.logger import get_module_logger # noqa: E402 +from src.plugins.schedule.offline_llm import LLMModel # noqa: E402 + +logger = get_module_logger("scheduler") + + +class ScheduleGenerator: + enable_output: bool = True + + def __init__(self, name: str = "bot_name", personality: str = "你是一个爱国爱党的新时代青年", behavior: str = "你非常外向,喜欢尝试新事物和人交流"): + # 使用离线LLM模型 + self.llm_scheduler = LLMModel(model_name="Pro/deepseek-ai/DeepSeek-V3", temperature=0.9) + + self.today_schedule_text = "" + self.today_done_list = [] + + self.yesterday_schedule_text = "" + self.yesterday_done_list = [] + + self.name = name + self.personality = personality + self.behavior = behavior + + self.start_time = datetime.datetime.now() + + async def mai_schedule_start(self): + """启动日程系统,每5分钟执行一次move_doing,并在日期变化时重新检查日程""" + try: + logger.info(f"日程系统启动/刷新时间: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}") + # 初始化日程 + await self.check_and_create_today_schedule() + self.print_schedule() + + while True: + current_time = datetime.datetime.now() + + # 检查是否需要重新生成日程(日期变化) + if current_time.date() != self.start_time.date(): + logger.info("检测到日期变化,重新生成日程") + self.start_time = current_time + await self.check_and_create_today_schedule() + self.print_schedule() + + # 执行当前活动 + current_activity = await self.move_doing() + logger.info(f"当前活动: {current_activity}") + + # 等待5分钟 + await asyncio.sleep(300) # 300秒 = 5分钟 + + except Exception as e: + logger.error(f"日程系统运行时出错: {str(e)}") + logger.exception("详细错误信息:") + + async def check_and_create_today_schedule(self): + """检查昨天的日程,并确保今天有日程安排 + + Returns: + tuple: (today_schedule_text, today_schedule) 今天的日程文本和解析后的日程字典 + """ + today = datetime.datetime.now() + yesterday = today - datetime.timedelta(days=1) + + # 先检查昨天的日程 + self.yesterday_schedule_text, self.yesterday_done_list = self.load_schedule_from_db(yesterday) + if self.yesterday_schedule_text: + logger.debug(f"已加载{yesterday.strftime('%Y-%m-%d')}的日程") + + # 检查今天的日程 + self.today_schedule_text, self.today_done_list = self.load_schedule_from_db(today) + if not self.today_schedule_text: + logger.info(f"{today.strftime('%Y-%m-%d')}的日程不存在,准备生成新的日程") + self.today_schedule_text = await self.generate_daily_schedule(target_date=today) + + self.save_today_schedule_to_db() + + def construct_daytime_prompt(self, target_date: datetime.datetime): + date_str = target_date.strftime("%Y-%m-%d") + weekday = target_date.strftime("%A") + + prompt = f"我是{self.name},{self.personality},{self.behavior}" + prompt += f"我昨天的日程是:{self.yesterday_schedule_text}\n" + prompt += f"请为我生成{date_str}({weekday})的日程安排,结合我的个人特点和行为习惯\n" + prompt += "推测我的日程安排,包括我一天都在做什么,有什么发现和思考,具体一些,详细一些,记得写明时间\n" + prompt += "直接返回我的日程,不要输出其他内容:" + return prompt + + def construct_doing_prompt(self,time: datetime.datetime): + now_time = time.strftime("%H:%M") + previous_doing = self.today_done_list[-20:] if len(self.today_done_list) > 20 else self.today_done_list + prompt = f"我是{self.name},{self.personality},{self.behavior}" + prompt += f"我今天的日程是:{self.today_schedule_text}\n" + prompt += f"我之前做了的事情是:{previous_doing}\n" + prompt += f"现在是{now_time},结合我的个人特点和行为习惯," + prompt += "推测我现在做什么,具体一些,详细一些\n" + prompt += "直接返回我在做的事情,不要输出其他内容:" + return prompt + + async def generate_daily_schedule( + self, target_date: datetime.datetime = None,) -> Dict[str, str]: + daytime_prompt = self.construct_daytime_prompt(target_date) + daytime_response, _ = await self.llm_scheduler.generate_response(daytime_prompt) + return daytime_response + + def _time_diff(self, time1: str, time2: str) -> int: + """计算两个时间字符串之间的分钟差""" + if time1 == "24:00": + time1 = "23:59" + if time2 == "24:00": + time2 = "23:59" + t1 = datetime.datetime.strptime(time1, "%H:%M") + t2 = datetime.datetime.strptime(time2, "%H:%M") + diff = int((t2 - t1).total_seconds() / 60) + # 考虑时间的循环性 + if diff < -720: + diff += 1440 # 加一天的分钟 + elif diff > 720: + diff -= 1440 # 减一天的分钟 + # print(f"时间1[{time1}]: 时间2[{time2}],差值[{diff}]分钟") + return diff + + def print_schedule(self): + """打印完整的日程安排""" + if not self.today_schedule_text: + logger.warning("今日日程有误,将在下次运行时重新生成") + db.schedule.delete_one({"date": datetime.datetime.now().strftime("%Y-%m-%d")}) + else: + logger.info("=== 今日日程安排 ===") + logger.info(self.today_schedule_text) + logger.info("==================") + self.enable_output = False + + async def update_today_done_list(self): + # 更新数据库中的 today_done_list + today_str = datetime.datetime.now().strftime("%Y-%m-%d") + existing_schedule = db.schedule.find_one({"date": today_str}) + + if existing_schedule: + # 更新数据库中的 today_done_list + db.schedule.update_one( + {"date": today_str}, + {"$set": {"today_done_list": self.today_done_list}} + ) + logger.debug(f"已更新{today_str}的已完成活动列表") + else: + logger.warning(f"未找到{today_str}的日程记录") + + async def move_doing(self): + current_time = datetime.datetime.now() + time_str = current_time.strftime("%H:%M") + doing_prompt = self.construct_doing_prompt(current_time) + doing_response, _ = await self.llm_scheduler.generate_response(doing_prompt) + self.today_done_list.append(current_time,time_str + "在" + doing_response) + + await self.update_today_done_list() + + return doing_response + + + + + def save_today_schedule_to_db(self): + """保存日程到数据库,同时初始化 today_done_list""" + date_str = datetime.datetime.now().strftime("%Y-%m-%d") + schedule_data = { + "date": date_str, + "schedule": self.today_schedule_text, + "today_done_list": self.today_done_list if hasattr(self, 'today_done_list') else [] + } + # 使用 upsert 操作,如果存在则更新,不存在则插入 + db.schedule.update_one( + {"date": date_str}, + {"$set": schedule_data}, + upsert=True + ) + logger.debug(f"已保存{date_str}的日程到数据库") + + def load_schedule_from_db(self, date: datetime.datetime): + """从数据库加载日程,同时加载 today_done_list""" + date_str = date.strftime("%Y-%m-%d") + existing_schedule = db.schedule.find_one({"date": date_str}) + + if existing_schedule: + schedule_text = existing_schedule["schedule"] + return schedule_text, existing_schedule.get("today_done_list", []) + else: + logger.debug(f"{date_str}的日程不存在") + return None, None + +async def main(): + # 使用示例 + scheduler = ScheduleGenerator(name="麦麦", personality="你叫麦麦,你19岁,是一个大二的女大学生,你有一头黑色短发,你会刷贴吧,你现在在学习心理学", behavior="你比较内向") + await scheduler.check_and_create_today_schedule() + scheduler.print_schedule() + print("\n当前任务:") + print(await scheduler.get_current_task()) + + print("昨天日程:") + print(scheduler.yesterday_schedule) + print("今天日程:") + print(scheduler.today_schedule) + print("明天日程:") + print(scheduler.tomorrow_schedule) + +# 当作为组件导入时使用的实例 +bot_schedule = ScheduleGenerator() + +if __name__ == "__main__": + import asyncio + # 当直接运行此文件时执行 + asyncio.run(main())