import tomli import sys from pathlib import Path from typing import Dict, Any, List, 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']}' " f"可能拼写错误,应为 '{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("提供商 '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']}", " '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至少需要1项," f"当前长度: {len(personality['prompt_personality'])}" ) 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 # noqa: E501, F821 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") 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按任意键退出...")