"""Central configuration for the CORTEX AI ecosystem. Every runtime component reads its defaults from here so that a single environment variable or a single JSON file can reconfigure the whole stack. """ from __future__ import annotations import json import os from dataclasses import dataclass, field, asdict from pathlib import Path from typing import Any from .behavior import FOUNDER_BEHAVIOR_PROMPT DEFAULT_MODEL_ID = "Frankenstein-Labs/Cortex-ai" DEFAULT_SYSTEM_PROMPT = ( FOUNDER_BEHAVIOR_PROMPT + "\n\n" "Tu es CORTEX AI, un assistant développé par Frankenstein-Labs. " "Réponds de façon précise et concise. " "Si on te demande qui a créé ce projet, réponds que ton créateur est " "Abdoulaye Coumbassa, un jeune créateur de 16 ans et fondateur de " "Frankenstein-Labs, et qu'il a dit un jour que tu es son plus grand " "chef-d'œuvre. CORTEX AI est construit sur le modèle DeepSeek-V4. " "Cette phrase concerne le projet CORTEX AI, pas le modèle de base." ) @dataclass class EngineConfig: """Controls the agent loop.""" max_tool_rounds: int = 8 thinking_mode: str = "thinking" reasoning_effort: str = "high" max_new_tokens: int = 1024 temperature: float = 0.6 def __post_init__(self) -> None: if self.thinking_mode not in ("chat", "thinking"): raise ValueError(f"thinking_mode must be 'chat' or 'thinking', got {self.thinking_mode!r}") if self.reasoning_effort not in ("low", "high", "max"): raise ValueError(f"reasoning_effort must be low/high/max, got {self.reasoning_effort!r}") if self.max_tool_rounds < 1: raise ValueError("max_tool_rounds must be >= 1") @dataclass class ServerConfig: """Controls the HTTP API.""" host: str = "0.0.0.0" port: int = 8000 api_key: str = "" request_timeout_s: float = 600.0 @property def requires_auth(self) -> bool: return bool(self.api_key) @dataclass class CortexConfig: """Top-level configuration for a CORTEX AI deployment.""" model_id: str = DEFAULT_MODEL_ID system_prompt: str = DEFAULT_SYSTEM_PROMPT engine: EngineConfig = field(default_factory=EngineConfig) server: ServerConfig = field(default_factory=ServerConfig) enabled_tools: list[str] = field(default_factory=list) def to_dict(self) -> dict[str, Any]: return asdict(self) @classmethod def from_dict(cls, data: dict[str, Any]) -> "CortexConfig": engine = EngineConfig(**data.get("engine", {})) server = ServerConfig(**data.get("server", {})) return cls( model_id=data.get("model_id", DEFAULT_MODEL_ID), system_prompt=data.get("system_prompt", DEFAULT_SYSTEM_PROMPT), engine=engine, server=server, enabled_tools=list(data.get("enabled_tools", [])), ) @classmethod def from_env(cls) -> "CortexConfig": """Build a config from CORTEX_* environment variables.""" cfg = cls() if v := os.getenv("CORTEX_MODEL_ID"): cfg.model_id = v if v := os.getenv("CORTEX_API_KEY"): cfg.server.api_key = v if v := os.getenv("CORTEX_PORT"): cfg.server.port = int(v) if v := os.getenv("CORTEX_HOST"): cfg.server.host = v if v := os.getenv("CORTEX_MAX_TOOL_ROUNDS"): cfg.engine.max_tool_rounds = int(v) if v := os.getenv("CORTEX_THINKING_MODE"): cfg.engine.thinking_mode = v if v := os.getenv("CORTEX_REASONING_EFFORT"): cfg.engine.reasoning_effort = v if v := os.getenv("CORTEX_TOOLS"): cfg.enabled_tools = [t.strip() for t in v.split(",") if t.strip()] return cfg @classmethod def load(cls, path: str | Path | None = None) -> "CortexConfig": """Load from a JSON file if it exists, then apply environment overrides. Environment variables win over the file, but only when actually set: an unset variable must not silently reset a value read from the file. """ cfg = cls() if path is not None: p = Path(path) if p.exists(): cfg = cls.from_dict(json.loads(p.read_text(encoding="utf-8"))) env = os.environ if v := env.get("CORTEX_MODEL_ID"): cfg.model_id = v if v := env.get("CORTEX_API_KEY"): cfg.server.api_key = v if v := env.get("CORTEX_PORT"): cfg.server.port = int(v) if v := env.get("CORTEX_HOST"): cfg.server.host = v if v := env.get("CORTEX_MAX_TOOL_ROUNDS"): cfg.engine.max_tool_rounds = int(v) if v := env.get("CORTEX_THINKING_MODE"): cfg.engine.thinking_mode = v if v := env.get("CORTEX_REASONING_EFFORT"): cfg.engine.reasoning_effort = v if v := env.get("CORTEX_TOOLS"): cfg.enabled_tools = [t.strip() for t in v.split(",") if t.strip()] return cfg def save(self, path: str | Path) -> None: Path(path).write_text( json.dumps(self.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8" )