import os from pathlib import Path import yaml from dotenv import load_dotenv load_dotenv() def _yaml_get(*keys, default=None): _CONFIG_PATH = Path(__file__).resolve().parent / "config.yaml" _yaml: dict = {} if _CONFIG_PATH.is_file(): with open(_CONFIG_PATH, "r", encoding="utf-8") as f: _yaml = yaml.safe_load(f) or {} d = _yaml for k in keys: if isinstance(d, dict) and k in d: d = d[k] else: return default return d if d is not None else default # Credential / Secret (only from .env) llm_baseurl = os.getenv("LLM_BASE_URL", default="") llm_model = os.getenv("LLM_MODEL", default="") llm_api_key = os.getenv("LLM_API_KEY", default="") llm_timeout = int(_yaml_get("llm", "timeout", default=600)) XMPP_USERNAME = os.getenv("XMPP_USERNAME", default="") XMPP_PASSWORD = os.getenv("XMPP_PASSWORD", default="") MODELS_ITEMS: list[dict] = [] _models_config = _yaml_get("models", default={}) _items = _models_config.get("items", []) if isinstance(_items, list): for entry in _items: if not isinstance(entry, dict): continue model = entry.get("model", "") provider = entry.get("provider", "") base_url = entry.get("base_url", "").rstrip("/") api_key = entry.get("key", "") or llm_api_key MODELS_ITEMS.append({ "model": model, "provider": provider, "base_url": base_url, "api_key": api_key, "default": entry.get("default", False), }) def resolve_provider(base_url: str, model: str) -> str | None: """Cari nama provider yg cocok dengan (base_url, model) dari MODELS_ITEMS.""" base_url = base_url.rstrip("/") for item in MODELS_ITEMS: if item["base_url"] == base_url and item["model"] == model: return item["provider"] return None _has_match = any( item["model"] == llm_model and item["base_url"] == llm_baseurl.rstrip("/") for item in MODELS_ITEMS ) if not _has_match: for item in MODELS_ITEMS: if item.get("default"): llm_model = item["model"] llm_baseurl = item["base_url"] llm_api_key = item["api_key"] break AGENT_MAX_ITERATIONS = int(os.getenv("AGENT_MAX_ITERATIONS", default=_yaml_get("agent", "max_iterations", default="30"))) AGENT_MAX_TOOL_OUTPUT = int(os.getenv("AGENT_MAX_TOOL_OUTPUT", default=_yaml_get("agent", "max_tool_output", default="40000"))) AGENT_SKILL = os.getenv("AGENT_SKILL", default="programmer").strip().lower() PERSONA_NAME = os.getenv("PERSONA_NAME", default=_yaml_get("persona", "name", default="Hendrik")).strip() or "Hendrik" PERSONA_AGE = os.getenv("PERSONA_AGE", default=_yaml_get("persona", "age", default="")).strip() PERSONA_GENDER = os.getenv("PERSONA_GENDER", default=_yaml_get("persona", "gender", default="")).strip() PERSONA_TONE = os.getenv("PERSONA_TONE", default=_yaml_get("persona", "tone", default="casual")).strip().lower() or "casual" PERSONA_VERBOSITY = os.getenv("PERSONA_VERBOSITY", default=_yaml_get("persona", "verbosity", default="balanced")).strip().lower() or "balanced" PERSONA_HUMOR = os.getenv("PERSONA_HUMOR", default=_yaml_get("persona", "humor", default="light")).strip().lower() or "light" PERSONA_LANGUAGE = os.getenv("PERSONA_LANGUAGE", default=_yaml_get("persona", "language", default="id")).strip().lower() or "id" PERSONA_MOOD = os.getenv("PERSONA_MOOD", default=_yaml_get("persona", "mood", default="cheerful")).strip().lower() or "cheerful" PERSONA_CATCHPHRASES = os.getenv("PERSONA_CATCHPHRASES", default=_yaml_get("persona", "catchphrases", default="")).strip() # ─── Character & Skills (YAML, bisa di-override dari .env) ───────────────────── AGENT_CHARACTER = os.getenv("AGENT_CHARACTER", default=_yaml_get("agent", "character", default="")).strip().lower() AGENT_SKILLS = os.getenv("AGENT_SKILLS", default="").strip().lower() # ─── XMPP (non-credential dari YAML, credential dari .env) ───────────────────── XMPP_ENABLED = os.getenv("XMPP_ENABLED", default=str(_yaml_get("xmpp", "enabled", default="false"))).strip().lower() in ("true", "1", "yes") XMPP_MUC_ROOMS = os.getenv("XMPP_MUC_ROOMS", default=_yaml_get("xmpp", "muc_rooms", default="")).strip() XMPP_NICKNAME = os.getenv("XMPP_NICKNAME", default=_yaml_get("xmpp", "nickname", default="")).strip() XMPP_SELECTIVE_RESPONSE = os.getenv("XMPP_SELECTIVE_RESPONSE", default=str(_yaml_get("xmpp", "selective_response", default="true"))).strip().lower() in ("true", "1", "yes") # ─── RAG (YAML) ───────────────────────────────────────────────────────────────── RAG_PERSIST_DIR = os.getenv("RAG_PERSIST_DIR", default=_yaml_get("rag", "persist_dir", default="chroma_db")) # ─── Humanize Delay (YAML) ───────────────────────────────────────────────────── READ_DELAY_MIN = float(os.getenv("READ_DELAY_MIN", default=_yaml_get("delay", "read_min", default="1.0"))) READ_DELAY_MAX = float(os.getenv("READ_DELAY_MAX", default=_yaml_get("delay", "read_max", default="2.0"))) TYPING_SPEED = float(os.getenv("TYPING_SPEED", default=_yaml_get("delay", "typing_speed", default="15.0"))) TYPING_MAX = float(os.getenv("TYPING_MAX", default=_yaml_get("delay", "typing_max", default="10.0"))) # ─── Character Preset Override ────────────────────────────────────────────────── # Jika AGENT_CHARACTER di-set, baca character.md dari agent/characters// # dan override nilai persona yang relevan. ENV_CHARACTERS_DIR = Path(__file__).resolve().parent / "agent" / "characters" ENV_CHARACTER_CONFIG_PATH = ENV_CHARACTERS_DIR / AGENT_CHARACTER / "character.md" if AGENT_CHARACTER else None # Coba persona.yaml dulu (prioritas utama), kalau tidak ada fallback ke character.md _persona_yaml_path = ENV_CHARACTERS_DIR / AGENT_CHARACTER / "persona.yaml" if AGENT_CHARACTER else None if _persona_yaml_path and _persona_yaml_path.is_file(): # Prioritas utama: baca persona.yaml dari character directory try: _character_env = yaml.safe_load(_persona_yaml_path.read_text(encoding="utf-8")) or {} if not isinstance(_character_env, dict): _character_env = {} except Exception as _e: print(f"[config] Warning: gagal load persona.yaml untuk '{AGENT_CHARACTER}': {_e}") _character_env = {} elif ENV_CHARACTER_CONFIG_PATH and ENV_CHARACTER_CONFIG_PATH.is_file(): # Fallback: baca character.md (format lama) _character_env = {} for line in ENV_CHARACTER_CONFIG_PATH.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line or line.startswith("#"): continue if "=" in line: key, value = line.split("=", 1) _character_env[key.strip()] = value.strip() else: _character_env = None if _character_env: _character_overrides = { "AGENT_SKILL": AGENT_SKILL, "PERSONA_NAME": PERSONA_NAME, "PERSONA_AGE": PERSONA_AGE, "PERSONA_GENDER": PERSONA_GENDER, "PERSONA_TONE": PERSONA_TONE, "PERSONA_VERBOSITY": PERSONA_VERBOSITY, "PERSONA_HUMOR": PERSONA_HUMOR, "PERSONA_LANGUAGE": PERSONA_LANGUAGE, "PERSONA_MOOD": PERSONA_MOOD, "PERSONA_CATCHPHRASES": PERSONA_CATCHPHRASES, } # Mapping dari key persona.yaml ke key config _yaml_to_config_key = { "AGENT_SKILL": "skill", "PERSONA_NAME": "name", "PERSONA_AGE": "age", "PERSONA_GENDER": "gender", "PERSONA_TONE": "tone", "PERSONA_VERBOSITY": "verbosity", "PERSONA_HUMOR": "humor", "PERSONA_LANGUAGE": "language", "PERSONA_MOOD": "mood", "PERSONA_CATCHPHRASES": "catchphrases", } for key, fallback in _character_overrides.items(): yaml_key = _yaml_to_config_key.get(key, key.lower()) raw = _character_env.get(yaml_key, _character_env.get(key, fallback)) value = str(raw).strip() if raw is not None else "" if key in {"AGENT_SKILL", "PERSONA_TONE", "PERSONA_VERBOSITY", "PERSONA_HUMOR", "PERSONA_LANGUAGE", "PERSONA_MOOD"}: value = value.lower() or fallback if key == "PERSONA_NAME" and not value: value = fallback _character_overrides[key] = value for key, value in _character_overrides.items(): globals()[key] = value os.environ[key] = value