mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-08-28 05:15:00 +00:00
177 lines
No EOL
5.6 KiB
Python
177 lines
No EOL
5.6 KiB
Python
import threading
|
|
from pathlib import Path
|
|
from typing import Union, Iterable, Dict, Any, Optional
|
|
|
|
from .grounding import GroundingConfig
|
|
from .constants import (
|
|
CONFIG_GROUNDING,
|
|
CONFIG_SECURITY,
|
|
CONFIG_DEV,
|
|
CONFIG_MCP,
|
|
CONFIG_AGENTS
|
|
)
|
|
from openspace.utils.logging import Logger
|
|
from .utils import load_json_file, save_json_file as save_json
|
|
|
|
logger = Logger.get_logger(__name__)
|
|
|
|
|
|
CONFIG_DIR = Path(__file__).parent
|
|
|
|
# Global configuration singleton
|
|
_config: GroundingConfig | None = None
|
|
_config_lock = threading.RLock() # Use RLock to support recursive locking
|
|
|
|
|
|
def _deep_merge_dict(base: dict, update: dict) -> dict:
|
|
"""Deep merge two dictionaries, update's values will override base's values"""
|
|
result = base.copy()
|
|
for key, value in update.items():
|
|
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
result[key] = _deep_merge_dict(result[key], value)
|
|
else:
|
|
result[key] = value
|
|
return result
|
|
|
|
def _load_json_file(path: Path) -> Dict[str, Any]:
|
|
"""Load single JSON configuration file.
|
|
|
|
This function wraps the generic load_json_file and adds global configuration specific error handling and logging.
|
|
"""
|
|
if not path.exists():
|
|
logger.debug(f"Configuration file does not exist, skipping: {path}")
|
|
return {}
|
|
|
|
try:
|
|
data = load_json_file(path)
|
|
logger.info(f"Loaded configuration file: {path}")
|
|
return data
|
|
except Exception as e:
|
|
logger.warning(f"Failed to load configuration file {path}: {e}")
|
|
return {}
|
|
|
|
def _load_multiple_files(paths: Iterable[Path]) -> Dict[str, Any]:
|
|
"""Load configuration from multiple files"""
|
|
merged = {}
|
|
for path in paths:
|
|
data = _load_json_file(path)
|
|
if data:
|
|
merged = _deep_merge_dict(merged, data)
|
|
return merged
|
|
|
|
def load_config(*config_paths: Union[str, Path]) -> GroundingConfig:
|
|
"""
|
|
Load configuration files
|
|
"""
|
|
global _config
|
|
|
|
with _config_lock:
|
|
if config_paths:
|
|
paths = [Path(p) for p in config_paths]
|
|
else:
|
|
paths = [
|
|
CONFIG_DIR / CONFIG_GROUNDING,
|
|
CONFIG_DIR / CONFIG_SECURITY,
|
|
CONFIG_DIR / CONFIG_DEV, # Optional: development environment configuration
|
|
]
|
|
|
|
# Load and merge configuration
|
|
raw_data = _load_multiple_files(paths)
|
|
|
|
# Load MCP configuration (separate processing)
|
|
# Check if mcpServers already provided in merged custom configs
|
|
has_custom_mcp_servers = "mcpServers" in raw_data
|
|
|
|
if has_custom_mcp_servers:
|
|
# Use mcpServers from custom config
|
|
if "mcp" not in raw_data:
|
|
raw_data["mcp"] = {}
|
|
raw_data["mcp"]["servers"] = raw_data.pop("mcpServers")
|
|
logger.debug(f"Using custom MCP servers from provided config ({len(raw_data['mcp']['servers'])} servers)")
|
|
else:
|
|
# Load default MCP servers from config_mcp.json
|
|
mcp_data = _load_json_file(CONFIG_DIR / CONFIG_MCP)
|
|
if mcp_data and "mcpServers" in mcp_data:
|
|
if "mcp" not in raw_data:
|
|
raw_data["mcp"] = {}
|
|
raw_data["mcp"]["servers"] = mcp_data["mcpServers"]
|
|
logger.debug(f"Loaded MCP servers from default config_mcp.json ({len(raw_data['mcp']['servers'])} servers)")
|
|
|
|
# Validate and create configuration object
|
|
try:
|
|
_config = GroundingConfig.model_validate(raw_data)
|
|
except Exception as e:
|
|
logger.error(f"Validation failed, using default configuration: {e}")
|
|
_config = GroundingConfig()
|
|
|
|
# Adjust log level according to configuration
|
|
if _config.debug:
|
|
Logger.set_debug(2)
|
|
elif _config.log_level:
|
|
try:
|
|
Logger.configure(level=_config.log_level)
|
|
except Exception as e:
|
|
logger.warning(f"Failed to set log level {_config.log_level}: {e}")
|
|
|
|
return _config
|
|
|
|
def get_config() -> GroundingConfig:
|
|
"""
|
|
Get global configuration instance.
|
|
|
|
Usage:
|
|
- Get configuration in Provider: get_config().get_backend_config('shell')
|
|
- Get security policy in Tool: get_config().get_security_policy('shell')
|
|
"""
|
|
global _config
|
|
|
|
if _config is None:
|
|
with _config_lock:
|
|
if _config is None:
|
|
load_config()
|
|
|
|
return _config
|
|
|
|
def reset_config() -> None:
|
|
"""Reset configuration (for testing)"""
|
|
global _config
|
|
with _config_lock:
|
|
_config = None
|
|
|
|
def save_config(config: GroundingConfig, path: Union[str, Path]) -> None:
|
|
save_json(config.model_dump(), path)
|
|
logger.info(f"Configuration saved to: {path}")
|
|
|
|
|
|
def load_agents_config() -> Dict[str, Any]:
|
|
agents_config_path = CONFIG_DIR / CONFIG_AGENTS
|
|
return _load_json_file(agents_config_path)
|
|
|
|
|
|
def get_agent_config(agent_name: str) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Get the configuration of the specified agent
|
|
"""
|
|
agents_config = load_agents_config()
|
|
|
|
if "agents" not in agents_config:
|
|
logger.warning(f"No 'agents' key found in {CONFIG_AGENTS}")
|
|
return None
|
|
|
|
for agent_cfg in agents_config.get("agents", []):
|
|
if agent_cfg.get("name") == agent_name:
|
|
return agent_cfg
|
|
|
|
logger.warning(f"Agent '{agent_name}' not found in {CONFIG_AGENTS}")
|
|
return None
|
|
|
|
|
|
__all__ = [
|
|
"CONFIG_DIR",
|
|
"load_config",
|
|
"get_config",
|
|
"reset_config",
|
|
"save_config",
|
|
"load_agents_config",
|
|
"get_agent_config"
|
|
] |