125 lines
4.9 KiB
Python
125 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
from functools import lru_cache
|
|
|
|
|
|
def _bool(name: str, default: bool) -> bool:
|
|
value = os.getenv(name)
|
|
if value is None:
|
|
return default
|
|
return value.strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
def _int(name: str, default: int) -> int:
|
|
value = os.getenv(name)
|
|
return int(value) if value else default
|
|
|
|
|
|
def _float(name: str, default: float) -> float:
|
|
value = os.getenv(name)
|
|
return float(value) if value else default
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Settings:
|
|
model_api_base_url: str
|
|
model_api_key: str
|
|
deepseek_api_base_url: str
|
|
deepseek_api_key: str
|
|
openwebui_forward_jwt_secret: str
|
|
internal_provider_key: str
|
|
internal_gateway_key: str
|
|
database_url: str
|
|
redis_url: str
|
|
gateway_url: str
|
|
execution_provider: str
|
|
workspace_image: str
|
|
workspace_network_enabled: bool
|
|
workspace_memory_limit: str
|
|
workspace_cpu_limit: float
|
|
workspace_pids_limit: int
|
|
workspace_ssh_host: str
|
|
workspace_download_max_bytes: int
|
|
model_timeout_seconds: int
|
|
tool_timeout_seconds: int
|
|
max_tool_output_chars: int
|
|
|
|
@classmethod
|
|
def from_env(cls) -> Settings:
|
|
return cls(
|
|
model_api_base_url=os.getenv("MODEL_API_BASE_URL", "https://api.k1412.top").rstrip("/"),
|
|
model_api_key=os.getenv("MODEL_API_KEY", ""),
|
|
deepseek_api_base_url=os.getenv("DEEPSEEK_API_BASE_URL", "https://api.deepseek.com").rstrip("/"),
|
|
deepseek_api_key=os.getenv("DEEPSEEK_API_KEY", ""),
|
|
openwebui_forward_jwt_secret=os.getenv("OPENWEBUI_FORWARD_JWT_SECRET", ""),
|
|
internal_provider_key=os.getenv("INTERNAL_PROVIDER_KEY", ""),
|
|
internal_gateway_key=os.getenv("INTERNAL_GATEWAY_KEY", ""),
|
|
database_url=os.getenv("DATABASE_URL", "sqlite+aiosqlite:///./.runtime/agent.db"),
|
|
redis_url=os.getenv("REDIS_URL", "redis://localhost:6379/0"),
|
|
gateway_url=os.getenv("GATEWAY_URL", "http://gateway:8001").rstrip("/"),
|
|
execution_provider=os.getenv("EXECUTION_PROVIDER", "local-docker"),
|
|
workspace_image=os.getenv("WORKSPACE_IMAGE", "k1412-agent-workspace:dev"),
|
|
workspace_network_enabled=_bool("WORKSPACE_NETWORK_ENABLED", True),
|
|
workspace_memory_limit=os.getenv("WORKSPACE_MEMORY_LIMIT", "2g"),
|
|
workspace_cpu_limit=_float("WORKSPACE_CPU_LIMIT", 2.0),
|
|
workspace_pids_limit=_int("WORKSPACE_PIDS_LIMIT", 512),
|
|
workspace_ssh_host=os.getenv("WORKSPACE_SSH_HOST", ""),
|
|
workspace_download_max_bytes=_int("WORKSPACE_DOWNLOAD_MAX_BYTES", 128 * 1024 * 1024),
|
|
model_timeout_seconds=_int("MODEL_TIMEOUT_SECONDS", 600),
|
|
tool_timeout_seconds=_int("TOOL_TIMEOUT_SECONDS", 120),
|
|
max_tool_output_chars=_int("MAX_TOOL_OUTPUT_CHARS", 24_000),
|
|
)
|
|
|
|
def validate_runtime(self) -> None:
|
|
missing = [
|
|
name
|
|
for name, value in (
|
|
("MODEL_API_KEY", self.model_api_key),
|
|
("DEEPSEEK_API_KEY", self.deepseek_api_key),
|
|
("OPENWEBUI_FORWARD_JWT_SECRET", self.openwebui_forward_jwt_secret),
|
|
("INTERNAL_PROVIDER_KEY", self.internal_provider_key),
|
|
("INTERNAL_GATEWAY_KEY", self.internal_gateway_key),
|
|
)
|
|
if not value
|
|
]
|
|
if missing:
|
|
raise RuntimeError(f"Missing required runtime secrets: {', '.join(missing)}")
|
|
self._validate_internal_secret_lengths()
|
|
|
|
def validate_gateway(self) -> None:
|
|
missing = [
|
|
name
|
|
for name, value in (
|
|
("OPENWEBUI_FORWARD_JWT_SECRET", self.openwebui_forward_jwt_secret),
|
|
("INTERNAL_GATEWAY_KEY", self.internal_gateway_key),
|
|
)
|
|
if not value
|
|
]
|
|
if missing:
|
|
raise RuntimeError(f"Missing required gateway secrets: {', '.join(missing)}")
|
|
self._validate_internal_secret_lengths()
|
|
if self.execution_provider not in {"local-docker", "ssh-docker"}:
|
|
raise RuntimeError("EXECUTION_PROVIDER must be local-docker or ssh-docker")
|
|
if self.execution_provider == "ssh-docker" and not self.workspace_ssh_host:
|
|
raise RuntimeError("WORKSPACE_SSH_HOST is required for ssh-docker")
|
|
|
|
def _validate_internal_secret_lengths(self) -> None:
|
|
weak = [
|
|
name
|
|
for name, value in (
|
|
("OPENWEBUI_FORWARD_JWT_SECRET", self.openwebui_forward_jwt_secret),
|
|
("INTERNAL_PROVIDER_KEY", self.internal_provider_key),
|
|
("INTERNAL_GATEWAY_KEY", self.internal_gateway_key),
|
|
)
|
|
if value and len(value.encode("utf-8")) < 32
|
|
]
|
|
if weak:
|
|
raise RuntimeError(f"Internal secrets must be at least 32 bytes: {', '.join(weak)}")
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def get_settings() -> Settings:
|
|
return Settings.from_env()
|