Files

1 line
83 KiB
JSON

{"repo": "agi-hub/agiagent", "files": {"src/mem/src/utils/config.py": {"978ba49a221caa947268149f1887f6f11ac4afc6": {"t": 1785746867.7691612, "n": 27284, "h": "6cafb5cfb1aab6ae", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (c) 2025 AGI Agent Research Group.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\nConfiguration Management Module\n\"\"\"\n\nimport os\nimport json\nimport time\nimport shutil\nfrom typing import Dict, Any, Tuple, Optional, List, Union\nfrom pathlib import Path\n\nfrom .exceptions import ConfigError\nfrom .logger import get_logger\nfrom .config_validator import ConfigValidator\n\nlogger = get_logger(__name__)\n\n\nclass ConfigLoader:\n \"\"\"Configuration loader class for unified management of all configuration parameters\"\"\"\n\n def __init__(self, config_file: Optional[Union[str, List[str]]] = None, auto_reload: bool = False):\n \"\"\"\n Initialize configuration loader\n\n Args:\n config_file: Configuration file path(s), can be a string or list of strings.\n If None, will use default: [\"config/config.txt\", \"config/config_mem.txt\"]\n auto_reload: Whether to enable auto-reload\n \"\"\"\n # Handle different input types and set default config files\n if config_file is None:\n # Default configuration: try to find config files in standard locations\n self.config_files = self._get_default_config_files()\n elif isinstance(config_file, str):\n # Single config file (backward compatibility)\n self.config_files = [config_file]\n elif isinstance(config_file, list):\n # Multiple config files\n self.config_files = config_file\n else:\n raise ValueError(\"config_file must be a string, list of strings, or None\")\n \n # For backward compatibility, keep config_file attribute\n self.config_file = self.config_files[0] if self.config_files else \"config.txt\"\n \n self.auto_reload = auto_reload\n self.last_modified = {} # Track modification times for each file\n self.config = {}\n self.env_config = {}\n\n # Load environment variable configuration\n self._load_env_config()\n\n # Load configuration files\n self._load_config()\n\n # Validate configuration\n self._validate_config()\n\n def _get_default_config_files(self) -> List[str]:\n \"\"\"\n Get default configuration file paths\n \n Returns:\n List of default configuration file paths\n \"\"\"\n # Try to determine project root directory\n current_dir = os.path.dirname(os.path.abspath(__file__))\n \n # Navigate up to find project root (look for config directory)\n project_root = current_dir\n for _ in range(10): # Limit search depth\n config_dir = os.path.join(project_root, \"config\")\n if os.path.exists(config_dir):\n break\n parent = os.path.dirname(project_root)\n if parent == project_root: # Reached filesystem root\n break\n project_root = parent\n else:\n # Fallback: assume config is in current directory or parent directories\n project_root = current_dir\n \n # Default config files in order of preference\n default_files = [\n os.path.join(project_root, \"config\", \"config.txt\"),\n os.path.join(project_root, \"config\", \"config_memory.txt\"),\n # Fallback options\n os.path.join(project_root, \"config.txt\"),\n \"config.txt\"\n ]\n \n # Only return files that exist, or at least the first one as fallback\n existing_files = [f for f in default_files if os.path.exists(f)]\n if existing_files:\n return existing_files\n else:\n # Return the first default file even if it doesn't exist\n return [default_files[0]]\n\n def _load_env_config(self) -> Dict[str, str]:\n \"\"\"Load configuration from environment variables\"\"\"\n env_config = {}\n\n # Define environment variable mapping\n env_mapping = {\n \"MEM_API_KEY\": \"api_key\",\n \"MEM_API_BASE\": \"api_base\",\n \"MEM_MODEL\": \"mem_model\",\n \"MEM_EMBEDDING_MODEL\": \"embedding_model\",\n \"MEM_AGENT_MODEL\": \"agent_model\",\n \"MEM_MAX_TOKENS\": \"max_tokens\",\n \"MEM_SIMILARITY_THRESHOLD\": \"similarity_threshold\",\n \"MEM_DEFAULT_TOP_K\": \"default_top_k\",\n \"MEM_EMBEDDING_WEIGHT\": \"default_embedding_weight\",\n \"MEM_TFIDF_WEIGHT\": \"default_tfidf_weight\",\n \"MEM_MAX_DAYS_BACK\": \"default_max_days_back\",\n \"MEM_SORT_BY\": \"default_sort_by\",\n \"MEM_LLM_TOOL_TEMPERATURE\": \"llm_tool_temperature\",\n \"MEM_LLM_TOOL_MAX_TOKENS\": \"llm_tool_max_tokens\",\n \"MEM_LLM_TOOL_TOP_P\": \"llm_tool_top_p\",\n \"MEM_LLM_TIMEOUT\": \"llm_timeout\",\n \"MEM_STREAMING\": \"streaming\",\n \"MEM_LANG\": \"LANG\",\n \"MEM_LOG_ENABLED\": \"log_enabled\"\n }\n\n for env_var, config_key in env_mapping.items():\n value = os.getenv(env_var)\n if value:\n env_config[config_key] = value\n logger.debug(f\"Loaded configuration from environment variable: {config_key} = {value}\")\n\n self.env_config = env_config\n return env_config\n\n def _load_config(self) -> Dict[str, str]:\n \"\"\"\n Read configuration files and merge their contents\n\n Returns:\n Merged configuration dictionary\n \"\"\"\n config = {}\n \n # Load each configuration file in order\n for config_file in self.config_files:\n file_config = self._load_single_config_file(config_file)\n if file_config:\n # Merge configuration (later files override earlier ones)\n config.update(file_config)\n \n # If no config was loaded, use default configuration\n if not config:\n logger.warning(\"No configuration files could be loaded, using default configuration\")\n config = self._get_default_config()\n\n # Merge environment variable configuration (environment variables have highest priority)\n merged_config = config.copy()\n merged_config.update(self.env_config)\n\n self.config = merged_config\n return merged_config\n\n def _load_single_config_file(self, config_file: str) -> Optional[Dict[str, str]]:\n \"\"\"\n Load a single configuration file\n\n Args:\n config_file: Path to configuration file\n\n Returns:\n Configuration dictionary or None if failed\n \"\"\"\n config = {}\n try:\n # Check if file exists\n if not os.path.exists(config_file):\n logger.debug(f\"Configuration file does not exist: {config_file}\")\n return None\n \n # Check if file has been modified (for auto-reload)\n current_modified = os.path.getmtime(config_file)\n if self.auto_reload and config_file in self.last_modified:\n if current_modified > self.last_modified[config_file]:\n logger.info(f\"Configuration file update detected, reloading: {config_file}\")\n \n self.last_modified[config_file] = current_modified\n\n # Read configuration file\n with open(config_file, 'r', encoding='utf-8') as f:\n for line_num, line in enumerate(f, 1):\n line = line.strip()\n # Skip comments and empty lines\n if line and not line.startswith('#') and '=' in line:\n try:\n key, value = line.split('=', 1)\n config[key.strip()] = value.strip()\n except ValueError as e:\n logger.warning(f\"Configuration file {config_file} line {line_num} format error: {line}\")\n\n logger.info(f\"Successfully loaded configuration file: {config_file}\")\n return config\n\n except Exception as e:\n logger.error(f\"Failed to read configuration file {config_file}: {e}\")\n return None\n\n def reload_config(self) -> bool:\n \"\"\"\n Reload configuration\n\n Returns:\n Whether reload was successful\n \"\"\"\n try:\n logger.info(\"Starting configuration reload...\")\n\n # Reload environment variables\n self._load_env_config()\n\n # Reload configuration file\n self._load_config()\n\n # Re-validate configuration\n self._validate_config()\n\n logger.info(\"Configuration reload completed\")\n return True\n except Exception as e:\n logger.error(f\"Configuration reload failed: {e}\")\n return False\n\n def _validate_config(self):\n \"\"\"Validate configuration\"\"\"\n try:\n validation_result = self.validate_config()\n if not validation_result['valid']:\n logger.error(\"Configuration validation failed:\")\n for error in validation_result['errors']:\n logger.error(f\" - {error}\")\n\n # If required configuration error, throw exception\n if any('Missing required configuration' in error for error in validation_result['errors']):\n raise ConfigError(\"Configuration validation failed, missing required configuration items\")\n\n if validation_result['warnings']:\n logger.warning(\"Configuration warnings:\")\n for warning in validation_result['warnings']:\n logger.warning(f\" - {warning}\")\n\n except Exception as e:\n logger.error(f\"Error occurred during configuration validation: {e}\")\n # Don't throw exception, allow using default configuration\n\n def _get_default_config(self) -> Dict[str, str]:\n \"\"\"Get default configuration\"\"\"\n return {\n # API configuration\n 'api_key': '',\n 'api_base': 'https://api.siliconflow.cn/v1',\n 'mem_model': 'Pro/Qwen/Qwen2.5-7B-Instruct',\n 'max_tokens': '4096',\n 'embedding_model': 'Pro/BAAI/bge-m3',\n 'agent_model': 'deepseek-ai/DeepSeek-V3',\n 'streaming': 'True',\n\n # Truncation configuration\n 'truncation_length': '10000',\n 'history_truncation_length': '10000',\n 'web_content_truncation_length': '50000',\n\n # History summary configuration\n 'summary_history': 'True',\n 'summary_max_length': '5000',\n 'summary_trigger_length': '30000',\n\n # Memory management system configuration\n 'similarity_threshold': '0.70',\n 'default_top_k': '5',\n 'default_embedding_weight': '0.5',\n 'default_tfidf_weight': '0.5',\n 'default_max_days_back': '30',\n 'default_sort_by': 'recall_count',\n\n # LLM tool call configuration\n 'llm_tool_temperature': '0.0',\n 'llm_tool_max_tokens': '500',\n 'llm_tool_top_p': '0.1',\n 'llm_timeout': '10',\n\n # Language configuration\n 'LANG': 'zh',\n 'log_enabled': 'True'\n }\n\n def get(self, key: str, default: Any = None) -> Any:\n \"\"\"\n Get configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Configuration value\n \"\"\"\n # If auto-reload is enabled, check if any config file has been modified\n if self.auto_reload:\n should_reload = False\n for config_file in self.config_files:\n if os.path.exists(config_file):\n current_modified = os.path.getmtime(config_file)\n if config_file not in self.last_modified or current_modified > self.last_modified[config_file]:\n should_reload = True\n break\n if should_reload:\n self.reload_config()\n\n return self.config.get(key, default)\n\n def get_int(self, key: str, default: int = 0) -> int:\n \"\"\"\n Get integer configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Integer configuration value\n \"\"\"\n try:\n return int(self.get(key, default))\n except (ValueError, TypeError):\n logger.warning(f\"Configuration item {key} cannot be converted to integer, using default value {default}\")\n return default\n\n def get_float(self, key: str, default: float = 0.0) -> float:\n \"\"\"\n Get float configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Float configuration value\n \"\"\"\n try:\n return float(self.get(key, default))\n except (ValueError, TypeError):\n logger.warning(f\"Configuration item {key} cannot be converted to float, using default value {default}\")\n return default\n\n def get_bool(self, key: str, default: bool = False) -> bool:\n \"\"\"\n Get boolean configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Boolean configuration value\n \"\"\"\n value = self.get(key, str(default)).lower()\n return value in ('true', '1', 'yes', 'on')\n\n def get_tuple(self, key1: str, key2: str, default: Tuple[float, float] = (0.5, 0.5)) -> Tuple[float, float]:\n \"\"\"\n Get tuple configuration value (for weights, etc.)\n\n Args:\n key1: First configuration key\n key2: Second configuration key\n default: Default value\n\n Returns:\n Tuple configuration value\n \"\"\"\n try:\n val1 = self.get_float(key1, default[0])\n val2 = self.get_float(key2, default[1])\n return (val1, val2)\n except Exception as e:\n logger.warning(f\"Failed to get tuple configuration: {e}, using default value {default}\")\n return default\n\n # Memory management system specific configuration methods\n def get_similarity_threshold(self) -> float:\n \"\"\"Get similarity threshold\"\"\"\n return self.get_float('similarity_threshold', 0.70)\n\n def get_max_tokens(self) -> int:\n \"\"\"Get maximum token count\"\"\"\n return self.get_int('max_tokens', 4096)\n\n def get_default_top_k(self) -> int:\n \"\"\"Get default search result count\"\"\"\n return self.get_int('default_top_k', 5)\n\n def get_default_weights(self) -> Tuple[float, float]:\n \"\"\"Get default search weights\"\"\"\n return self.get_tuple('default_embedding_weight', 'default_tfidf_weight', (0.5, 0.5))\n\n def get_default_max_days_back(self) -> int:\n \"\"\"Get default memoir update days\"\"\"\n return self.get_int('default_max_days_back', 30)\n\n def get_default_sort_by(self) -> str:\n \"\"\"Get default sorting method\"\"\"\n return self.get('default_sort_by', 'recall_count')\n\n def get_llm_tool_params(self) -> Dict[str, Any]:\n \"\"\"Get LLM tool call parameters\"\"\"\n return {\n 'temperature': self.get_float('llm_tool_temperature', 0.0),\n 'max_tokens': self.get_int('llm_tool_max_tokens', 500),\n 'top_p': self.get_float('llm_tool_top_p', 0.1),\n 'timeout': self.get_int('llm_timeout', 10)\n }\n\n def get_api_config(self) -> Dict[str, str]:\n \"\"\"Get API configuration\"\"\"\n return {\n 'api_key': self.get('api_key'),\n 'api_base': self.get('api_base'),\n 'model': self.get('mem_model'),\n 'embedding_model': self.get('embedding_model'),\n 'agent_model': self.get('agent_model')\n }\n\n def get_truncation_config(self) -> Dict[str, int]:\n \"\"\"Get truncation configuration\"\"\"\n return {\n 'truncation_length': self.get_int('truncation_length', 10000),\n 'history_truncation_length': self.get_int('history_truncation_length', 10000),\n 'web_content_truncation_length': self.get_int('web_content_truncation_length', 50000)\n }\n\n def get_summary_config(self) -> Dict[str, Any]:\n \"\"\"Get summary configuration\"\"\"\n return {\n 'summary_history': self.get_bool('summary_history', True),\n 'summary_max_length': self.get_int('summary_max_length', 5000),\n 'summary_trigger_length': self.get_int('summary_trigger_length', 30000)\n }\n\n def get_log_config(self) -> Dict[str, Any]:\n \"\"\"Get logging configuration\"\"\"\n return {\n 'log_enabled': self.get_bool('log_enabled', True)\n }\n\n def validate_config(self) -> Dict[str, Any]:\n \"\"\"\n Validate configuration validity\n\n Returns:\n Validation result dictionary\n \"\"\"\n validation_result = {\n 'valid': True,\n 'errors': [],\n 'warnings': [],\n 'fixes': []\n }\n\n # Check required configurations\n required_configs = ['api_key', 'api_base',\n 'mem_model', 'embedding_model']\n for config in required_configs:\n if not self.get(config):\n validation_result['valid'] = False\n validation_result['errors'].append(f\"Missing required configuration: {config}\")\n # Provide fix suggestions\n if config == 'api_key':\n validation_result['fixes'].append(\n f\"Please set {config} or environment variable MEM_API_KEY\")\n elif config == 'api_base':\n validation_result['fixes'].append(\n f\"Please set {config} or environment variable MEM_API_BASE\")\n else:\n validation_result['fixes'].append(f\"Please set {config} or the corresponding environment variable\")\n\n # Check value ranges\n similarity_threshold = self.get_similarity_threshold()\n if not 0.0 <= similarity_threshold <= 1.0:\n validation_result['warnings'].append(\n f\"Similarity threshold should be between 0.0-1.0, current value: {similarity_threshold}\")\n validation_result['fixes'].append(\n \"It is recommended to set similarity_threshold to a value between 0.0-1.0\")\n\n max_tokens = self.get_max_tokens()\n if max_tokens <= 0:\n validation_result['warnings'].append(\n f\"Maximum token count should be greater than 0, current value: {max_tokens}\")\n validation_result['fixes'].append(\"It is recommended to set max_tokens to a positive integer\")\n\n # Check weight configuration\n weights = self.get_default_weights()\n if abs(sum(weights) - 1.0) > 0.01:\n validation_result['warnings'].append(\n f\"Search weights should sum up to approximately 1.0, current value: {sum(weights)}\")\n validation_result['fixes'].append(\n \"It is recommended to adjust default_embedding_weight and default_tfidf_weight to sum to 1.0\")\n\n # Check API key format\n api_key = self.get('api_key')\n if api_key and not api_key.startswith('sk-'):\n validation_result['warnings'].append(\"API key format may be incorrect, should start with 'sk-'\")\n\n return validation_result\n\n def print_config_summary(self):\n \"\"\"Print configuration summary\"\"\"\n logger.info(\"=== Configuration Summary ===\")\n logger.info(f\"Configuration file: {self.config_file}\")\n logger.info(f\"Auto-reload: {'Enabled' if self.auto_reload else 'Disabled'}\")\n logger.info(f\"Environment variable configurations: {len(self.env_config)}\")\n logger.info(f\"Similarity threshold: {self.get_similarity_threshold()}\")\n logger.info(f\"Maximum token count: {self.get_max_tokens()}\")\n logger.info(f\"Default search result count: {self.get_default_top_k()}\")\n logger.info(f\"Default search weights: {self.get_default_weights()}\")\n logger.info(f\"Default sorting method: {self.get_default_sort_by()}\")\n logger.info(f\"Default memoir update days: {self.get_default_max_days_back()}\")\n\n # Validate configuration\n validation = self.validate_config()\n if not validation['valid']:\n logger.error(\"Configuration validation failed:\")\n for error in validation['errors']:\n logger.error(f\" - {error}\")\n\n if validation['warnings']:\n logger.warning(\"Configuration warnings:\")\n for warning in validation['warnings']:\n logger.warning(f\" - {warning}\")\n\n if validation['fixes']:\n logger.info(\"Fix suggestions:\")\n for fix in validation['fixes']:\n logger.info(f\" - {fix}\")\n\n if validation['valid'] and not validation['warnings']:\n logger.info(\"Configuration validation passed\")\n\n def save_config(self, config_dict: Dict[str, Any], backup: bool = True) -> bool:\n \"\"\"\n Save configuration to file\n\n Args:\n config_dict: Configuration dictionary to save\n backup: Whether to backup original file\n\n Returns:\n Whether saving was successful\n \"\"\"\n try:\n # Backup original file\n if backup and os.path.exists(self.config_file):\n backup_file = f\"{self.config_file}.backup.{int(time.time())}\"\n shutil.copy2(self.config_file, backup_file)\n logger.info(f\"Configuration file backed up: {backup_file}\")\n\n # Ensure directory exists\n os.makedirs(os.path.dirname(self.config_file), exist_ok=True)\n\n # Save configuration\n with open(self.config_file, 'w', encoding='utf-8') as f:\n f.write(\"# Intelligent Memory Management System Configuration File\\n\")\n f.write(f\"# Generated time: {time.strftime('%Y-%m-%d %H:%M:%S')}\\n\\n\")\n\n for key, value in config_dict.items():\n f.write(f\"{key}={value}\\n\")\n\n logger.info(f\"Configuration saved to: {self.config_file}\")\n return True\n except Exception as e:\n logger.error(f\"Failed to save configuration: {e}\")\n return False\n\n def update_config(self, updates: Dict[str, Any]) -> bool:\n \"\"\"\n Update configuration\n\n Args:\n updates: Configuration dictionary to update\n\n Returns:\n Whether updating was successful\n \"\"\"\n try:\n # Read existing configuration\n current_config = {}\n if os.path.exists(self.config_file):\n with open(self.config_file, 'r', encoding='utf-8') as f:\n for line in f:\n line = line.strip()\n if line and not line.startswith('#') and '=' in line:\n key, value = line.split('=', 1)\n current_config[key.strip()] = value.strip()\n\n # Update configuration\n current_config.update(updates)\n\n # Save updated configuration\n return self.save_config(current_config)\n except Exception as e:\n logger.error(f\"Failed to update configuration: {e}\")\n return False\n\n def get_config_source(self, key: str) -> str:\n \"\"\"\n Get configuration value source\n\n Args:\n key: Configuration key\n\n Returns:\n Configuration source ('file', 'env', 'default')\n \"\"\"\n if key in self.env_config:\n return 'env'\n elif key in self.config:\n return 'file'\n else:\n return 'default'\n\n def export_config(self, format: str = 'json') -> str:\n \"\"\"\n Export configuration\n\n Args:\n format: Export format ('json', 'env')\n\n Returns:\n Exported configuration string\n \"\"\"\n try:\n if format == 'json':\n return json.dumps(self.config, ensure_ascii=False, indent=2)\n elif format == 'env':\n env_lines = []\n for key, value in self.config.items():\n env_lines.append(f\"{key.upper()}={value}\")\n return '\\n'.join(env_lines)\n else:\n raise ValueError(f\"Unsupported export format: {format}\")\n except Exception as e:\n logger.error(f\"Failed to export configuration: {e}\")\n return \"\"\n\n def import_config(self, config_data: str, format: str = 'json') -> bool:\n \"\"\"\n Import configuration\n\n Args:\n config_data: Configuration data string\n format: Data format ('json', 'env')\n\n Returns:\n Whether importing was successful\n \"\"\"\n try:\n if format == 'json':\n imported_config = json.loads(config_data)\n elif format == 'env':\n imported_config = {}\n for line in config_data.split('\\n'):\n line = line.strip()\n if line and '=' in line and not line.startswith('#'):\n key, value = line.split('=', 1)\n imported_config[key.lower()] = value\n else:\n raise ValueError(f\"Unsupported import format: {format}\")\n\n # Update configuration\n self.config.update(imported_config)\n\n # Save to file\n return self.save_config(self.config)\n except Exception as e:\n logger.error(f\"Failed to import configuration: {e}\")\n return False\n\n def reset_to_defaults(self) -> bool:\n \"\"\"\n Reset to default configuration\n\n Returns:\n Whether resetting was successful\n \"\"\"\n try:\n default_config = self._get_default_config()\n return self.save_config(default_config)\n except Exception as e:\n logger.error(f\"Failed to reset configuration: {e}\")\n return False\n\n def get_config_stats(self) -> Dict[str, Any]:\n \"\"\"\n Get configuration statistics\n\n Returns:\n Configuration statistics\n \"\"\"\n stats = {\n 'total_configs': len(self.config),\n 'file_configs': len([k for k in self.config.keys() if k not in self.env_config]),\n 'env_configs': len(self.env_config),\n 'default_configs': len(self._get_default_config()),\n 'validation_result': self.validate_config()\n }\n return stats\n"}, "e598c024e25e7236cb9ba8a0ee40b2bf90d48b27": {"t": 1785768246.3744206, "n": 27284, "h": "6cafb5cfb1aab6ae", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (c) 2025 AGI Agent Research Group.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\nConfiguration Management Module\n\"\"\"\n\nimport os\nimport json\nimport time\nimport shutil\nfrom typing import Dict, Any, Tuple, Optional, List, Union\nfrom pathlib import Path\n\nfrom .exceptions import ConfigError\nfrom .logger import get_logger\nfrom .config_validator import ConfigValidator\n\nlogger = get_logger(__name__)\n\n\nclass ConfigLoader:\n \"\"\"Configuration loader class for unified management of all configuration parameters\"\"\"\n\n def __init__(self, config_file: Optional[Union[str, List[str]]] = None, auto_reload: bool = False):\n \"\"\"\n Initialize configuration loader\n\n Args:\n config_file: Configuration file path(s), can be a string or list of strings.\n If None, will use default: [\"config/config.txt\", \"config/config_mem.txt\"]\n auto_reload: Whether to enable auto-reload\n \"\"\"\n # Handle different input types and set default config files\n if config_file is None:\n # Default configuration: try to find config files in standard locations\n self.config_files = self._get_default_config_files()\n elif isinstance(config_file, str):\n # Single config file (backward compatibility)\n self.config_files = [config_file]\n elif isinstance(config_file, list):\n # Multiple config files\n self.config_files = config_file\n else:\n raise ValueError(\"config_file must be a string, list of strings, or None\")\n \n # For backward compatibility, keep config_file attribute\n self.config_file = self.config_files[0] if self.config_files else \"config.txt\"\n \n self.auto_reload = auto_reload\n self.last_modified = {} # Track modification times for each file\n self.config = {}\n self.env_config = {}\n\n # Load environment variable configuration\n self._load_env_config()\n\n # Load configuration files\n self._load_config()\n\n # Validate configuration\n self._validate_config()\n\n def _get_default_config_files(self) -> List[str]:\n \"\"\"\n Get default configuration file paths\n \n Returns:\n List of default configuration file paths\n \"\"\"\n # Try to determine project root directory\n current_dir = os.path.dirname(os.path.abspath(__file__))\n \n # Navigate up to find project root (look for config directory)\n project_root = current_dir\n for _ in range(10): # Limit search depth\n config_dir = os.path.join(project_root, \"config\")\n if os.path.exists(config_dir):\n break\n parent = os.path.dirname(project_root)\n if parent == project_root: # Reached filesystem root\n break\n project_root = parent\n else:\n # Fallback: assume config is in current directory or parent directories\n project_root = current_dir\n \n # Default config files in order of preference\n default_files = [\n os.path.join(project_root, \"config\", \"config.txt\"),\n os.path.join(project_root, \"config\", \"config_memory.txt\"),\n # Fallback options\n os.path.join(project_root, \"config.txt\"),\n \"config.txt\"\n ]\n \n # Only return files that exist, or at least the first one as fallback\n existing_files = [f for f in default_files if os.path.exists(f)]\n if existing_files:\n return existing_files\n else:\n # Return the first default file even if it doesn't exist\n return [default_files[0]]\n\n def _load_env_config(self) -> Dict[str, str]:\n \"\"\"Load configuration from environment variables\"\"\"\n env_config = {}\n\n # Define environment variable mapping\n env_mapping = {\n \"MEM_API_KEY\": \"api_key\",\n \"MEM_API_BASE\": \"api_base\",\n \"MEM_MODEL\": \"mem_model\",\n \"MEM_EMBEDDING_MODEL\": \"embedding_model\",\n \"MEM_AGENT_MODEL\": \"agent_model\",\n \"MEM_MAX_TOKENS\": \"max_tokens\",\n \"MEM_SIMILARITY_THRESHOLD\": \"similarity_threshold\",\n \"MEM_DEFAULT_TOP_K\": \"default_top_k\",\n \"MEM_EMBEDDING_WEIGHT\": \"default_embedding_weight\",\n \"MEM_TFIDF_WEIGHT\": \"default_tfidf_weight\",\n \"MEM_MAX_DAYS_BACK\": \"default_max_days_back\",\n \"MEM_SORT_BY\": \"default_sort_by\",\n \"MEM_LLM_TOOL_TEMPERATURE\": \"llm_tool_temperature\",\n \"MEM_LLM_TOOL_MAX_TOKENS\": \"llm_tool_max_tokens\",\n \"MEM_LLM_TOOL_TOP_P\": \"llm_tool_top_p\",\n \"MEM_LLM_TIMEOUT\": \"llm_timeout\",\n \"MEM_STREAMING\": \"streaming\",\n \"MEM_LANG\": \"LANG\",\n \"MEM_LOG_ENABLED\": \"log_enabled\"\n }\n\n for env_var, config_key in env_mapping.items():\n value = os.getenv(env_var)\n if value:\n env_config[config_key] = value\n logger.debug(f\"Loaded configuration from environment variable: {config_key} = {value}\")\n\n self.env_config = env_config\n return env_config\n\n def _load_config(self) -> Dict[str, str]:\n \"\"\"\n Read configuration files and merge their contents\n\n Returns:\n Merged configuration dictionary\n \"\"\"\n config = {}\n \n # Load each configuration file in order\n for config_file in self.config_files:\n file_config = self._load_single_config_file(config_file)\n if file_config:\n # Merge configuration (later files override earlier ones)\n config.update(file_config)\n \n # If no config was loaded, use default configuration\n if not config:\n logger.warning(\"No configuration files could be loaded, using default configuration\")\n config = self._get_default_config()\n\n # Merge environment variable configuration (environment variables have highest priority)\n merged_config = config.copy()\n merged_config.update(self.env_config)\n\n self.config = merged_config\n return merged_config\n\n def _load_single_config_file(self, config_file: str) -> Optional[Dict[str, str]]:\n \"\"\"\n Load a single configuration file\n\n Args:\n config_file: Path to configuration file\n\n Returns:\n Configuration dictionary or None if failed\n \"\"\"\n config = {}\n try:\n # Check if file exists\n if not os.path.exists(config_file):\n logger.debug(f\"Configuration file does not exist: {config_file}\")\n return None\n \n # Check if file has been modified (for auto-reload)\n current_modified = os.path.getmtime(config_file)\n if self.auto_reload and config_file in self.last_modified:\n if current_modified > self.last_modified[config_file]:\n logger.info(f\"Configuration file update detected, reloading: {config_file}\")\n \n self.last_modified[config_file] = current_modified\n\n # Read configuration file\n with open(config_file, 'r', encoding='utf-8') as f:\n for line_num, line in enumerate(f, 1):\n line = line.strip()\n # Skip comments and empty lines\n if line and not line.startswith('#') and '=' in line:\n try:\n key, value = line.split('=', 1)\n config[key.strip()] = value.strip()\n except ValueError as e:\n logger.warning(f\"Configuration file {config_file} line {line_num} format error: {line}\")\n\n logger.info(f\"Successfully loaded configuration file: {config_file}\")\n return config\n\n except Exception as e:\n logger.error(f\"Failed to read configuration file {config_file}: {e}\")\n return None\n\n def reload_config(self) -> bool:\n \"\"\"\n Reload configuration\n\n Returns:\n Whether reload was successful\n \"\"\"\n try:\n logger.info(\"Starting configuration reload...\")\n\n # Reload environment variables\n self._load_env_config()\n\n # Reload configuration file\n self._load_config()\n\n # Re-validate configuration\n self._validate_config()\n\n logger.info(\"Configuration reload completed\")\n return True\n except Exception as e:\n logger.error(f\"Configuration reload failed: {e}\")\n return False\n\n def _validate_config(self):\n \"\"\"Validate configuration\"\"\"\n try:\n validation_result = self.validate_config()\n if not validation_result['valid']:\n logger.error(\"Configuration validation failed:\")\n for error in validation_result['errors']:\n logger.error(f\" - {error}\")\n\n # If required configuration error, throw exception\n if any('Missing required configuration' in error for error in validation_result['errors']):\n raise ConfigError(\"Configuration validation failed, missing required configuration items\")\n\n if validation_result['warnings']:\n logger.warning(\"Configuration warnings:\")\n for warning in validation_result['warnings']:\n logger.warning(f\" - {warning}\")\n\n except Exception as e:\n logger.error(f\"Error occurred during configuration validation: {e}\")\n # Don't throw exception, allow using default configuration\n\n def _get_default_config(self) -> Dict[str, str]:\n \"\"\"Get default configuration\"\"\"\n return {\n # API configuration\n 'api_key': '',\n 'api_base': 'https://api.siliconflow.cn/v1',\n 'mem_model': 'Pro/Qwen/Qwen2.5-7B-Instruct',\n 'max_tokens': '4096',\n 'embedding_model': 'Pro/BAAI/bge-m3',\n 'agent_model': 'deepseek-ai/DeepSeek-V3',\n 'streaming': 'True',\n\n # Truncation configuration\n 'truncation_length': '10000',\n 'history_truncation_length': '10000',\n 'web_content_truncation_length': '50000',\n\n # History summary configuration\n 'summary_history': 'True',\n 'summary_max_length': '5000',\n 'summary_trigger_length': '30000',\n\n # Memory management system configuration\n 'similarity_threshold': '0.70',\n 'default_top_k': '5',\n 'default_embedding_weight': '0.5',\n 'default_tfidf_weight': '0.5',\n 'default_max_days_back': '30',\n 'default_sort_by': 'recall_count',\n\n # LLM tool call configuration\n 'llm_tool_temperature': '0.0',\n 'llm_tool_max_tokens': '500',\n 'llm_tool_top_p': '0.1',\n 'llm_timeout': '10',\n\n # Language configuration\n 'LANG': 'zh',\n 'log_enabled': 'True'\n }\n\n def get(self, key: str, default: Any = None) -> Any:\n \"\"\"\n Get configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Configuration value\n \"\"\"\n # If auto-reload is enabled, check if any config file has been modified\n if self.auto_reload:\n should_reload = False\n for config_file in self.config_files:\n if os.path.exists(config_file):\n current_modified = os.path.getmtime(config_file)\n if config_file not in self.last_modified or current_modified > self.last_modified[config_file]:\n should_reload = True\n break\n if should_reload:\n self.reload_config()\n\n return self.config.get(key, default)\n\n def get_int(self, key: str, default: int = 0) -> int:\n \"\"\"\n Get integer configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Integer configuration value\n \"\"\"\n try:\n return int(self.get(key, default))\n except (ValueError, TypeError):\n logger.warning(f\"Configuration item {key} cannot be converted to integer, using default value {default}\")\n return default\n\n def get_float(self, key: str, default: float = 0.0) -> float:\n \"\"\"\n Get float configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Float configuration value\n \"\"\"\n try:\n return float(self.get(key, default))\n except (ValueError, TypeError):\n logger.warning(f\"Configuration item {key} cannot be converted to float, using default value {default}\")\n return default\n\n def get_bool(self, key: str, default: bool = False) -> bool:\n \"\"\"\n Get boolean configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Boolean configuration value\n \"\"\"\n value = self.get(key, str(default)).lower()\n return value in ('true', '1', 'yes', 'on')\n\n def get_tuple(self, key1: str, key2: str, default: Tuple[float, float] = (0.5, 0.5)) -> Tuple[float, float]:\n \"\"\"\n Get tuple configuration value (for weights, etc.)\n\n Args:\n key1: First configuration key\n key2: Second configuration key\n default: Default value\n\n Returns:\n Tuple configuration value\n \"\"\"\n try:\n val1 = self.get_float(key1, default[0])\n val2 = self.get_float(key2, default[1])\n return (val1, val2)\n except Exception as e:\n logger.warning(f\"Failed to get tuple configuration: {e}, using default value {default}\")\n return default\n\n # Memory management system specific configuration methods\n def get_similarity_threshold(self) -> float:\n \"\"\"Get similarity threshold\"\"\"\n return self.get_float('similarity_threshold', 0.70)\n\n def get_max_tokens(self) -> int:\n \"\"\"Get maximum token count\"\"\"\n return self.get_int('max_tokens', 4096)\n\n def get_default_top_k(self) -> int:\n \"\"\"Get default search result count\"\"\"\n return self.get_int('default_top_k', 5)\n\n def get_default_weights(self) -> Tuple[float, float]:\n \"\"\"Get default search weights\"\"\"\n return self.get_tuple('default_embedding_weight', 'default_tfidf_weight', (0.5, 0.5))\n\n def get_default_max_days_back(self) -> int:\n \"\"\"Get default memoir update days\"\"\"\n return self.get_int('default_max_days_back', 30)\n\n def get_default_sort_by(self) -> str:\n \"\"\"Get default sorting method\"\"\"\n return self.get('default_sort_by', 'recall_count')\n\n def get_llm_tool_params(self) -> Dict[str, Any]:\n \"\"\"Get LLM tool call parameters\"\"\"\n return {\n 'temperature': self.get_float('llm_tool_temperature', 0.0),\n 'max_tokens': self.get_int('llm_tool_max_tokens', 500),\n 'top_p': self.get_float('llm_tool_top_p', 0.1),\n 'timeout': self.get_int('llm_timeout', 10)\n }\n\n def get_api_config(self) -> Dict[str, str]:\n \"\"\"Get API configuration\"\"\"\n return {\n 'api_key': self.get('api_key'),\n 'api_base': self.get('api_base'),\n 'model': self.get('mem_model'),\n 'embedding_model': self.get('embedding_model'),\n 'agent_model': self.get('agent_model')\n }\n\n def get_truncation_config(self) -> Dict[str, int]:\n \"\"\"Get truncation configuration\"\"\"\n return {\n 'truncation_length': self.get_int('truncation_length', 10000),\n 'history_truncation_length': self.get_int('history_truncation_length', 10000),\n 'web_content_truncation_length': self.get_int('web_content_truncation_length', 50000)\n }\n\n def get_summary_config(self) -> Dict[str, Any]:\n \"\"\"Get summary configuration\"\"\"\n return {\n 'summary_history': self.get_bool('summary_history', True),\n 'summary_max_length': self.get_int('summary_max_length', 5000),\n 'summary_trigger_length': self.get_int('summary_trigger_length', 30000)\n }\n\n def get_log_config(self) -> Dict[str, Any]:\n \"\"\"Get logging configuration\"\"\"\n return {\n 'log_enabled': self.get_bool('log_enabled', True)\n }\n\n def validate_config(self) -> Dict[str, Any]:\n \"\"\"\n Validate configuration validity\n\n Returns:\n Validation result dictionary\n \"\"\"\n validation_result = {\n 'valid': True,\n 'errors': [],\n 'warnings': [],\n 'fixes': []\n }\n\n # Check required configurations\n required_configs = ['api_key', 'api_base',\n 'mem_model', 'embedding_model']\n for config in required_configs:\n if not self.get(config):\n validation_result['valid'] = False\n validation_result['errors'].append(f\"Missing required configuration: {config}\")\n # Provide fix suggestions\n if config == 'api_key':\n validation_result['fixes'].append(\n f\"Please set {config} or environment variable MEM_API_KEY\")\n elif config == 'api_base':\n validation_result['fixes'].append(\n f\"Please set {config} or environment variable MEM_API_BASE\")\n else:\n validation_result['fixes'].append(f\"Please set {config} or the corresponding environment variable\")\n\n # Check value ranges\n similarity_threshold = self.get_similarity_threshold()\n if not 0.0 <= similarity_threshold <= 1.0:\n validation_result['warnings'].append(\n f\"Similarity threshold should be between 0.0-1.0, current value: {similarity_threshold}\")\n validation_result['fixes'].append(\n \"It is recommended to set similarity_threshold to a value between 0.0-1.0\")\n\n max_tokens = self.get_max_tokens()\n if max_tokens <= 0:\n validation_result['warnings'].append(\n f\"Maximum token count should be greater than 0, current value: {max_tokens}\")\n validation_result['fixes'].append(\"It is recommended to set max_tokens to a positive integer\")\n\n # Check weight configuration\n weights = self.get_default_weights()\n if abs(sum(weights) - 1.0) > 0.01:\n validation_result['warnings'].append(\n f\"Search weights should sum up to approximately 1.0, current value: {sum(weights)}\")\n validation_result['fixes'].append(\n \"It is recommended to adjust default_embedding_weight and default_tfidf_weight to sum to 1.0\")\n\n # Check API key format\n api_key = self.get('api_key')\n if api_key and not api_key.startswith('sk-'):\n validation_result['warnings'].append(\"API key format may be incorrect, should start with 'sk-'\")\n\n return validation_result\n\n def print_config_summary(self):\n \"\"\"Print configuration summary\"\"\"\n logger.info(\"=== Configuration Summary ===\")\n logger.info(f\"Configuration file: {self.config_file}\")\n logger.info(f\"Auto-reload: {'Enabled' if self.auto_reload else 'Disabled'}\")\n logger.info(f\"Environment variable configurations: {len(self.env_config)}\")\n logger.info(f\"Similarity threshold: {self.get_similarity_threshold()}\")\n logger.info(f\"Maximum token count: {self.get_max_tokens()}\")\n logger.info(f\"Default search result count: {self.get_default_top_k()}\")\n logger.info(f\"Default search weights: {self.get_default_weights()}\")\n logger.info(f\"Default sorting method: {self.get_default_sort_by()}\")\n logger.info(f\"Default memoir update days: {self.get_default_max_days_back()}\")\n\n # Validate configuration\n validation = self.validate_config()\n if not validation['valid']:\n logger.error(\"Configuration validation failed:\")\n for error in validation['errors']:\n logger.error(f\" - {error}\")\n\n if validation['warnings']:\n logger.warning(\"Configuration warnings:\")\n for warning in validation['warnings']:\n logger.warning(f\" - {warning}\")\n\n if validation['fixes']:\n logger.info(\"Fix suggestions:\")\n for fix in validation['fixes']:\n logger.info(f\" - {fix}\")\n\n if validation['valid'] and not validation['warnings']:\n logger.info(\"Configuration validation passed\")\n\n def save_config(self, config_dict: Dict[str, Any], backup: bool = True) -> bool:\n \"\"\"\n Save configuration to file\n\n Args:\n config_dict: Configuration dictionary to save\n backup: Whether to backup original file\n\n Returns:\n Whether saving was successful\n \"\"\"\n try:\n # Backup original file\n if backup and os.path.exists(self.config_file):\n backup_file = f\"{self.config_file}.backup.{int(time.time())}\"\n shutil.copy2(self.config_file, backup_file)\n logger.info(f\"Configuration file backed up: {backup_file}\")\n\n # Ensure directory exists\n os.makedirs(os.path.dirname(self.config_file), exist_ok=True)\n\n # Save configuration\n with open(self.config_file, 'w', encoding='utf-8') as f:\n f.write(\"# Intelligent Memory Management System Configuration File\\n\")\n f.write(f\"# Generated time: {time.strftime('%Y-%m-%d %H:%M:%S')}\\n\\n\")\n\n for key, value in config_dict.items():\n f.write(f\"{key}={value}\\n\")\n\n logger.info(f\"Configuration saved to: {self.config_file}\")\n return True\n except Exception as e:\n logger.error(f\"Failed to save configuration: {e}\")\n return False\n\n def update_config(self, updates: Dict[str, Any]) -> bool:\n \"\"\"\n Update configuration\n\n Args:\n updates: Configuration dictionary to update\n\n Returns:\n Whether updating was successful\n \"\"\"\n try:\n # Read existing configuration\n current_config = {}\n if os.path.exists(self.config_file):\n with open(self.config_file, 'r', encoding='utf-8') as f:\n for line in f:\n line = line.strip()\n if line and not line.startswith('#') and '=' in line:\n key, value = line.split('=', 1)\n current_config[key.strip()] = value.strip()\n\n # Update configuration\n current_config.update(updates)\n\n # Save updated configuration\n return self.save_config(current_config)\n except Exception as e:\n logger.error(f\"Failed to update configuration: {e}\")\n return False\n\n def get_config_source(self, key: str) -> str:\n \"\"\"\n Get configuration value source\n\n Args:\n key: Configuration key\n\n Returns:\n Configuration source ('file', 'env', 'default')\n \"\"\"\n if key in self.env_config:\n return 'env'\n elif key in self.config:\n return 'file'\n else:\n return 'default'\n\n def export_config(self, format: str = 'json') -> str:\n \"\"\"\n Export configuration\n\n Args:\n format: Export format ('json', 'env')\n\n Returns:\n Exported configuration string\n \"\"\"\n try:\n if format == 'json':\n return json.dumps(self.config, ensure_ascii=False, indent=2)\n elif format == 'env':\n env_lines = []\n for key, value in self.config.items():\n env_lines.append(f\"{key.upper()}={value}\")\n return '\\n'.join(env_lines)\n else:\n raise ValueError(f\"Unsupported export format: {format}\")\n except Exception as e:\n logger.error(f\"Failed to export configuration: {e}\")\n return \"\"\n\n def import_config(self, config_data: str, format: str = 'json') -> bool:\n \"\"\"\n Import configuration\n\n Args:\n config_data: Configuration data string\n format: Data format ('json', 'env')\n\n Returns:\n Whether importing was successful\n \"\"\"\n try:\n if format == 'json':\n imported_config = json.loads(config_data)\n elif format == 'env':\n imported_config = {}\n for line in config_data.split('\\n'):\n line = line.strip()\n if line and '=' in line and not line.startswith('#'):\n key, value = line.split('=', 1)\n imported_config[key.lower()] = value\n else:\n raise ValueError(f\"Unsupported import format: {format}\")\n\n # Update configuration\n self.config.update(imported_config)\n\n # Save to file\n return self.save_config(self.config)\n except Exception as e:\n logger.error(f\"Failed to import configuration: {e}\")\n return False\n\n def reset_to_defaults(self) -> bool:\n \"\"\"\n Reset to default configuration\n\n Returns:\n Whether resetting was successful\n \"\"\"\n try:\n default_config = self._get_default_config()\n return self.save_config(default_config)\n except Exception as e:\n logger.error(f\"Failed to reset configuration: {e}\")\n return False\n\n def get_config_stats(self) -> Dict[str, Any]:\n \"\"\"\n Get configuration statistics\n\n Returns:\n Configuration statistics\n \"\"\"\n stats = {\n 'total_configs': len(self.config),\n 'file_configs': len([k for k in self.config.keys() if k not in self.env_config]),\n 'env_configs': len(self.env_config),\n 'default_configs': len(self._get_default_config()),\n 'validation_result': self.validate_config()\n }\n return stats\n"}, "2b07a46f54fe5caeecf5990c3dfcf08054d46c08": {"t": 1785768248.6662145, "n": 26666, "h": "13ee0f74dbf6ff8f", "c": "\"\"\"\nConfiguration Management Module\n\"\"\"\n\nimport os\nimport json\nimport time\nimport shutil\nfrom typing import Dict, Any, Tuple, Optional, List, Union\nfrom pathlib import Path\n\nfrom .exceptions import ConfigError\nfrom .logger import get_logger\nfrom .config_validator import ConfigValidator\n\nlogger = get_logger(__name__)\n\n\nclass ConfigLoader:\n \"\"\"Configuration loader class for unified management of all configuration parameters\"\"\"\n\n def __init__(self, config_file: Optional[Union[str, List[str]]] = None, auto_reload: bool = False):\n \"\"\"\n Initialize configuration loader\n\n Args:\n config_file: Configuration file path(s), can be a string or list of strings.\n If None, will use default: [\"config/config.txt\", \"config/config_mem.txt\"]\n auto_reload: Whether to enable auto-reload\n \"\"\"\n # Handle different input types and set default config files\n if config_file is None:\n # Default configuration: try to find config files in standard locations\n self.config_files = self._get_default_config_files()\n elif isinstance(config_file, str):\n # Single config file (backward compatibility)\n self.config_files = [config_file]\n elif isinstance(config_file, list):\n # Multiple config files\n self.config_files = config_file\n else:\n raise ValueError(\"config_file must be a string, list of strings, or None\")\n \n # For backward compatibility, keep config_file attribute\n self.config_file = self.config_files[0] if self.config_files else \"config.txt\"\n \n self.auto_reload = auto_reload\n self.last_modified = {} # Track modification times for each file\n self.config = {}\n self.env_config = {}\n\n # Load environment variable configuration\n self._load_env_config()\n\n # Load configuration files\n self._load_config()\n\n # Validate configuration\n self._validate_config()\n\n def _get_default_config_files(self) -> List[str]:\n \"\"\"\n Get default configuration file paths\n \n Returns:\n List of default configuration file paths\n \"\"\"\n # Try to determine project root directory\n current_dir = os.path.dirname(os.path.abspath(__file__))\n \n # Navigate up to find project root (look for config directory)\n project_root = current_dir\n for _ in range(10): # Limit search depth\n config_dir = os.path.join(project_root, \"config\")\n if os.path.exists(config_dir):\n break\n parent = os.path.dirname(project_root)\n if parent == project_root: # Reached filesystem root\n break\n project_root = parent\n else:\n # Fallback: assume config is in current directory or parent directories\n project_root = current_dir\n \n # Default config files in order of preference\n default_files = [\n os.path.join(project_root, \"config\", \"config.txt\"),\n os.path.join(project_root, \"config\", \"config_memory.txt\"),\n # Fallback options\n os.path.join(project_root, \"config.txt\"),\n \"config.txt\"\n ]\n \n # Only return files that exist, or at least the first one as fallback\n existing_files = [f for f in default_files if os.path.exists(f)]\n if existing_files:\n return existing_files\n else:\n # Return the first default file even if it doesn't exist\n return [default_files[0]]\n\n def _load_env_config(self) -> Dict[str, str]:\n \"\"\"Load configuration from environment variables\"\"\"\n env_config = {}\n\n # Define environment variable mapping\n env_mapping = {\n \"MEM_API_KEY\": \"api_key\",\n \"MEM_API_BASE\": \"api_base\",\n \"MEM_MODEL\": \"mem_model\",\n \"MEM_EMBEDDING_MODEL\": \"embedding_model\",\n \"MEM_AGENT_MODEL\": \"agent_model\",\n \"MEM_MAX_TOKENS\": \"max_tokens\",\n \"MEM_SIMILARITY_THRESHOLD\": \"similarity_threshold\",\n \"MEM_DEFAULT_TOP_K\": \"default_top_k\",\n \"MEM_EMBEDDING_WEIGHT\": \"default_embedding_weight\",\n \"MEM_TFIDF_WEIGHT\": \"default_tfidf_weight\",\n \"MEM_MAX_DAYS_BACK\": \"default_max_days_back\",\n \"MEM_SORT_BY\": \"default_sort_by\",\n \"MEM_LLM_TOOL_TEMPERATURE\": \"llm_tool_temperature\",\n \"MEM_LLM_TOOL_MAX_TOKENS\": \"llm_tool_max_tokens\",\n \"MEM_LLM_TOOL_TOP_P\": \"llm_tool_top_p\",\n \"MEM_LLM_TIMEOUT\": \"llm_timeout\",\n \"MEM_STREAMING\": \"streaming\",\n \"MEM_LANG\": \"LANG\",\n \"MEM_LOG_ENABLED\": \"log_enabled\"\n }\n\n for env_var, config_key in env_mapping.items():\n value = os.getenv(env_var)\n if value:\n env_config[config_key] = value\n logger.debug(f\"Loaded configuration from environment variable: {config_key} = {value}\")\n\n self.env_config = env_config\n return env_config\n\n def _load_config(self) -> Dict[str, str]:\n \"\"\"\n Read configuration files and merge their contents\n\n Returns:\n Merged configuration dictionary\n \"\"\"\n config = {}\n \n # Load each configuration file in order\n for config_file in self.config_files:\n file_config = self._load_single_config_file(config_file)\n if file_config:\n # Merge configuration (later files override earlier ones)\n config.update(file_config)\n \n # If no config was loaded, use default configuration\n if not config:\n logger.warning(\"No configuration files could be loaded, using default configuration\")\n config = self._get_default_config()\n\n # Merge environment variable configuration (environment variables have highest priority)\n merged_config = config.copy()\n merged_config.update(self.env_config)\n\n self.config = merged_config\n return merged_config\n\n def _load_single_config_file(self, config_file: str) -> Optional[Dict[str, str]]:\n \"\"\"\n Load a single configuration file\n\n Args:\n config_file: Path to configuration file\n\n Returns:\n Configuration dictionary or None if failed\n \"\"\"\n config = {}\n try:\n # Check if file exists\n if not os.path.exists(config_file):\n logger.debug(f\"Configuration file does not exist: {config_file}\")\n return None\n \n # Check if file has been modified (for auto-reload)\n current_modified = os.path.getmtime(config_file)\n if self.auto_reload and config_file in self.last_modified:\n if current_modified > self.last_modified[config_file]:\n logger.info(f\"Configuration file update detected, reloading: {config_file}\")\n \n self.last_modified[config_file] = current_modified\n\n # Read configuration file\n with open(config_file, 'r', encoding='utf-8') as f:\n for line_num, line in enumerate(f, 1):\n line = line.strip()\n # Skip comments and empty lines\n if line and not line.startswith('#') and '=' in line:\n try:\n key, value = line.split('=', 1)\n config[key.strip()] = value.strip()\n except ValueError as e:\n logger.warning(f\"Configuration file {config_file} line {line_num} format error: {line}\")\n\n logger.info(f\"Successfully loaded configuration file: {config_file}\")\n return config\n\n except Exception as e:\n logger.error(f\"Failed to read configuration file {config_file}: {e}\")\n return None\n\n def reload_config(self) -> bool:\n \"\"\"\n Reload configuration\n\n Returns:\n Whether reload was successful\n \"\"\"\n try:\n logger.info(\"Starting configuration reload...\")\n\n # Reload environment variables\n self._load_env_config()\n\n # Reload configuration file\n self._load_config()\n\n # Re-validate configuration\n self._validate_config()\n\n logger.info(\"Configuration reload completed\")\n return True\n except Exception as e:\n logger.error(f\"Configuration reload failed: {e}\")\n return False\n\n def _validate_config(self):\n \"\"\"Validate configuration\"\"\"\n try:\n validation_result = self.validate_config()\n if not validation_result['valid']:\n logger.error(\"Configuration validation failed:\")\n for error in validation_result['errors']:\n logger.error(f\" - {error}\")\n\n # If required configuration error, throw exception\n if any('Missing required configuration' in error for error in validation_result['errors']):\n raise ConfigError(\"Configuration validation failed, missing required configuration items\")\n\n if validation_result['warnings']:\n logger.warning(\"Configuration warnings:\")\n for warning in validation_result['warnings']:\n logger.warning(f\" - {warning}\")\n\n except Exception as e:\n logger.error(f\"Error occurred during configuration validation: {e}\")\n # Don't throw exception, allow using default configuration\n\n def _get_default_config(self) -> Dict[str, str]:\n \"\"\"Get default configuration\"\"\"\n return {\n # API configuration\n 'api_key': '',\n 'api_base': 'https://api.siliconflow.cn/v1',\n 'mem_model': 'Pro/Qwen/Qwen2.5-7B-Instruct',\n 'max_tokens': '4096',\n 'embedding_model': 'Pro/BAAI/bge-m3',\n 'agent_model': 'deepseek-ai/DeepSeek-V3',\n 'streaming': 'True',\n\n # Truncation configuration\n 'truncation_length': '10000',\n 'history_truncation_length': '10000',\n 'web_content_truncation_length': '50000',\n\n # History summary configuration\n 'summary_history': 'True',\n 'summary_max_length': '5000',\n 'summary_trigger_length': '30000',\n\n # Memory management system configuration\n 'similarity_threshold': '0.70',\n 'default_top_k': '5',\n 'default_embedding_weight': '0.5',\n 'default_tfidf_weight': '0.5',\n 'default_max_days_back': '30',\n 'default_sort_by': 'recall_count',\n\n # LLM tool call configuration\n 'llm_tool_temperature': '0.0',\n 'llm_tool_max_tokens': '500',\n 'llm_tool_top_p': '0.1',\n 'llm_timeout': '10',\n\n # Language configuration\n 'LANG': 'zh',\n 'log_enabled': 'True'\n }\n\n def get(self, key: str, default: Any = None) -> Any:\n \"\"\"\n Get configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Configuration value\n \"\"\"\n # If auto-reload is enabled, check if any config file has been modified\n if self.auto_reload:\n should_reload = False\n for config_file in self.config_files:\n if os.path.exists(config_file):\n current_modified = os.path.getmtime(config_file)\n if config_file not in self.last_modified or current_modified > self.last_modified[config_file]:\n should_reload = True\n break\n if should_reload:\n self.reload_config()\n\n return self.config.get(key, default)\n\n def get_int(self, key: str, default: int = 0) -> int:\n \"\"\"\n Get integer configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Integer configuration value\n \"\"\"\n try:\n return int(self.get(key, default))\n except (ValueError, TypeError):\n logger.warning(f\"Configuration item {key} cannot be converted to integer, using default value {default}\")\n return default\n\n def get_float(self, key: str, default: float = 0.0) -> float:\n \"\"\"\n Get float configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Float configuration value\n \"\"\"\n try:\n return float(self.get(key, default))\n except (ValueError, TypeError):\n logger.warning(f\"Configuration item {key} cannot be converted to float, using default value {default}\")\n return default\n\n def get_bool(self, key: str, default: bool = False) -> bool:\n \"\"\"\n Get boolean configuration value\n\n Args:\n key: Configuration key\n default: Default value\n\n Returns:\n Boolean configuration value\n \"\"\"\n value = self.get(key, str(default)).lower()\n return value in ('true', '1', 'yes', 'on')\n\n def get_tuple(self, key1: str, key2: str, default: Tuple[float, float] = (0.5, 0.5)) -> Tuple[float, float]:\n \"\"\"\n Get tuple configuration value (for weights, etc.)\n\n Args:\n key1: First configuration key\n key2: Second configuration key\n default: Default value\n\n Returns:\n Tuple configuration value\n \"\"\"\n try:\n val1 = self.get_float(key1, default[0])\n val2 = self.get_float(key2, default[1])\n return (val1, val2)\n except Exception as e:\n logger.warning(f\"Failed to get tuple configuration: {e}, using default value {default}\")\n return default\n\n # Memory management system specific configuration methods\n def get_similarity_threshold(self) -> float:\n \"\"\"Get similarity threshold\"\"\"\n return self.get_float('similarity_threshold', 0.70)\n\n def get_max_tokens(self) -> int:\n \"\"\"Get maximum token count\"\"\"\n return self.get_int('max_tokens', 4096)\n\n def get_default_top_k(self) -> int:\n \"\"\"Get default search result count\"\"\"\n return self.get_int('default_top_k', 5)\n\n def get_default_weights(self) -> Tuple[float, float]:\n \"\"\"Get default search weights\"\"\"\n return self.get_tuple('default_embedding_weight', 'default_tfidf_weight', (0.5, 0.5))\n\n def get_default_max_days_back(self) -> int:\n \"\"\"Get default memoir update days\"\"\"\n return self.get_int('default_max_days_back', 30)\n\n def get_default_sort_by(self) -> str:\n \"\"\"Get default sorting method\"\"\"\n return self.get('default_sort_by', 'recall_count')\n\n def get_llm_tool_params(self) -> Dict[str, Any]:\n \"\"\"Get LLM tool call parameters\"\"\"\n return {\n 'temperature': self.get_float('llm_tool_temperature', 0.0),\n 'max_tokens': self.get_int('llm_tool_max_tokens', 500),\n 'top_p': self.get_float('llm_tool_top_p', 0.1),\n 'timeout': self.get_int('llm_timeout', 10)\n }\n\n def get_api_config(self) -> Dict[str, str]:\n \"\"\"Get API configuration\"\"\"\n return {\n 'api_key': self.get('api_key'),\n 'api_base': self.get('api_base'),\n 'model': self.get('mem_model'),\n 'embedding_model': self.get('embedding_model'),\n 'agent_model': self.get('agent_model')\n }\n\n def get_truncation_config(self) -> Dict[str, int]:\n \"\"\"Get truncation configuration\"\"\"\n return {\n 'truncation_length': self.get_int('truncation_length', 10000),\n 'history_truncation_length': self.get_int('history_truncation_length', 10000),\n 'web_content_truncation_length': self.get_int('web_content_truncation_length', 50000)\n }\n\n def get_summary_config(self) -> Dict[str, Any]:\n \"\"\"Get summary configuration\"\"\"\n return {\n 'summary_history': self.get_bool('summary_history', True),\n 'summary_max_length': self.get_int('summary_max_length', 5000),\n 'summary_trigger_length': self.get_int('summary_trigger_length', 30000)\n }\n\n def get_log_config(self) -> Dict[str, Any]:\n \"\"\"Get logging configuration\"\"\"\n return {\n 'log_enabled': self.get_bool('log_enabled', True)\n }\n\n def validate_config(self) -> Dict[str, Any]:\n \"\"\"\n Validate configuration validity\n\n Returns:\n Validation result dictionary\n \"\"\"\n validation_result = {\n 'valid': True,\n 'errors': [],\n 'warnings': [],\n 'fixes': []\n }\n\n # Check required configurations\n required_configs = ['api_key', 'api_base',\n 'mem_model', 'embedding_model']\n for config in required_configs:\n if not self.get(config):\n validation_result['valid'] = False\n validation_result['errors'].append(f\"Missing required configuration: {config}\")\n # Provide fix suggestions\n if config == 'api_key':\n validation_result['fixes'].append(\n f\"Please set {config} or environment variable MEM_API_KEY\")\n elif config == 'api_base':\n validation_result['fixes'].append(\n f\"Please set {config} or environment variable MEM_API_BASE\")\n else:\n validation_result['fixes'].append(f\"Please set {config} or the corresponding environment variable\")\n\n # Check value ranges\n similarity_threshold = self.get_similarity_threshold()\n if not 0.0 <= similarity_threshold <= 1.0:\n validation_result['warnings'].append(\n f\"Similarity threshold should be between 0.0-1.0, current value: {similarity_threshold}\")\n validation_result['fixes'].append(\n \"It is recommended to set similarity_threshold to a value between 0.0-1.0\")\n\n max_tokens = self.get_max_tokens()\n if max_tokens <= 0:\n validation_result['warnings'].append(\n f\"Maximum token count should be greater than 0, current value: {max_tokens}\")\n validation_result['fixes'].append(\"It is recommended to set max_tokens to a positive integer\")\n\n # Check weight configuration\n weights = self.get_default_weights()\n if abs(sum(weights) - 1.0) > 0.01:\n validation_result['warnings'].append(\n f\"Search weights should sum up to approximately 1.0, current value: {sum(weights)}\")\n validation_result['fixes'].append(\n \"It is recommended to adjust default_embedding_weight and default_tfidf_weight to sum to 1.0\")\n\n # Check API key format\n api_key = self.get('api_key')\n if api_key and not api_key.startswith('sk-'):\n validation_result['warnings'].append(\"API key format may be incorrect, should start with 'sk-'\")\n\n return validation_result\n\n def print_config_summary(self):\n \"\"\"Print configuration summary\"\"\"\n logger.info(\"=== Configuration Summary ===\")\n logger.info(f\"Configuration file: {self.config_file}\")\n logger.info(f\"Auto-reload: {'Enabled' if self.auto_reload else 'Disabled'}\")\n logger.info(f\"Environment variable configurations: {len(self.env_config)}\")\n logger.info(f\"Similarity threshold: {self.get_similarity_threshold()}\")\n logger.info(f\"Maximum token count: {self.get_max_tokens()}\")\n logger.info(f\"Default search result count: {self.get_default_top_k()}\")\n logger.info(f\"Default search weights: {self.get_default_weights()}\")\n logger.info(f\"Default sorting method: {self.get_default_sort_by()}\")\n logger.info(f\"Default memoir update days: {self.get_default_max_days_back()}\")\n\n # Validate configuration\n validation = self.validate_config()\n if not validation['valid']:\n logger.error(\"Configuration validation failed:\")\n for error in validation['errors']:\n logger.error(f\" - {error}\")\n\n if validation['warnings']:\n logger.warning(\"Configuration warnings:\")\n for warning in validation['warnings']:\n logger.warning(f\" - {warning}\")\n\n if validation['fixes']:\n logger.info(\"Fix suggestions:\")\n for fix in validation['fixes']:\n logger.info(f\" - {fix}\")\n\n if validation['valid'] and not validation['warnings']:\n logger.info(\"Configuration validation passed\")\n\n def save_config(self, config_dict: Dict[str, Any], backup: bool = True) -> bool:\n \"\"\"\n Save configuration to file\n\n Args:\n config_dict: Configuration dictionary to save\n backup: Whether to backup original file\n\n Returns:\n Whether saving was successful\n \"\"\"\n try:\n # Backup original file\n if backup and os.path.exists(self.config_file):\n backup_file = f\"{self.config_file}.backup.{int(time.time())}\"\n shutil.copy2(self.config_file, backup_file)\n logger.info(f\"Configuration file backed up: {backup_file}\")\n\n # Ensure directory exists\n os.makedirs(os.path.dirname(self.config_file), exist_ok=True)\n\n # Save configuration\n with open(self.config_file, 'w', encoding='utf-8') as f:\n f.write(\"# Intelligent Memory Management System Configuration File\\n\")\n f.write(f\"# Generated time: {time.strftime('%Y-%m-%d %H:%M:%S')}\\n\\n\")\n\n for key, value in config_dict.items():\n f.write(f\"{key}={value}\\n\")\n\n logger.info(f\"Configuration saved to: {self.config_file}\")\n return True\n except Exception as e:\n logger.error(f\"Failed to save configuration: {e}\")\n return False\n\n def update_config(self, updates: Dict[str, Any]) -> bool:\n \"\"\"\n Update configuration\n\n Args:\n updates: Configuration dictionary to update\n\n Returns:\n Whether updating was successful\n \"\"\"\n try:\n # Read existing configuration\n current_config = {}\n if os.path.exists(self.config_file):\n with open(self.config_file, 'r', encoding='utf-8') as f:\n for line in f:\n line = line.strip()\n if line and not line.startswith('#') and '=' in line:\n key, value = line.split('=', 1)\n current_config[key.strip()] = value.strip()\n\n # Update configuration\n current_config.update(updates)\n\n # Save updated configuration\n return self.save_config(current_config)\n except Exception as e:\n logger.error(f\"Failed to update configuration: {e}\")\n return False\n\n def get_config_source(self, key: str) -> str:\n \"\"\"\n Get configuration value source\n\n Args:\n key: Configuration key\n\n Returns:\n Configuration source ('file', 'env', 'default')\n \"\"\"\n if key in self.env_config:\n return 'env'\n elif key in self.config:\n return 'file'\n else:\n return 'default'\n\n def export_config(self, format: str = 'json') -> str:\n \"\"\"\n Export configuration\n\n Args:\n format: Export format ('json', 'env')\n\n Returns:\n Exported configuration string\n \"\"\"\n try:\n if format == 'json':\n return json.dumps(self.config, ensure_ascii=False, indent=2)\n elif format == 'env':\n env_lines = []\n for key, value in self.config.items():\n env_lines.append(f\"{key.upper()}={value}\")\n return '\\n'.join(env_lines)\n else:\n raise ValueError(f\"Unsupported export format: {format}\")\n except Exception as e:\n logger.error(f\"Failed to export configuration: {e}\")\n return \"\"\n\n def import_config(self, config_data: str, format: str = 'json') -> bool:\n \"\"\"\n Import configuration\n\n Args:\n config_data: Configuration data string\n format: Data format ('json', 'env')\n\n Returns:\n Whether importing was successful\n \"\"\"\n try:\n if format == 'json':\n imported_config = json.loads(config_data)\n elif format == 'env':\n imported_config = {}\n for line in config_data.split('\\n'):\n line = line.strip()\n if line and '=' in line and not line.startswith('#'):\n key, value = line.split('=', 1)\n imported_config[key.lower()] = value\n else:\n raise ValueError(f\"Unsupported import format: {format}\")\n\n # Update configuration\n self.config.update(imported_config)\n\n # Save to file\n return self.save_config(self.config)\n except Exception as e:\n logger.error(f\"Failed to import configuration: {e}\")\n return False\n\n def reset_to_defaults(self) -> bool:\n \"\"\"\n Reset to default configuration\n\n Returns:\n Whether resetting was successful\n \"\"\"\n try:\n default_config = self._get_default_config()\n return self.save_config(default_config)\n except Exception as e:\n logger.error(f\"Failed to reset configuration: {e}\")\n return False\n\n def get_config_stats(self) -> Dict[str, Any]:\n \"\"\"\n Get configuration statistics\n\n Returns:\n Configuration statistics\n \"\"\"\n stats = {\n 'total_configs': len(self.config),\n 'file_configs': len([k for k in self.config.keys() if k not in self.env_config]),\n 'env_configs': len(self.env_config),\n 'default_configs': len(self._get_default_config()),\n 'validation_result': self.validate_config()\n }\n return stats\n"}}}}