/
alexefan136
/
flowstack
Обзор
Документация
Войти
/
alexefan136
/
flowstack
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
core/engine/src/config.py
220 строк
8 KB
Alexander Efanov
upd fix
31 июл 2026, 19:17
31 июл 2026, 19:17
d146d86
Код
Авторство
О чём код?
"""Конфигурация engine через pydantic-settings. Приоритетный провайдер — Cloud.ru (GigaChat/DeepSeek через OpenAI-compatible API). Security: - API ключи хранятся как SecretStr (не попадают в логи/repr) - mcp_api_key — для аутентификации MCP JSON-RPC endpoint - gateway_secret — для проверки что запросы приходят от Gateway - В production все секреты через ENV (не в default значениях) """ from __future__ import annotations import warnings from functools import lru_cache from pathlib import Path from typing import Literal from pydantic import Field, SecretStr, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Основные настройки приложения.""" model_config = SettingsConfigDict( env_file=".env", env_prefix="FLOWSTACK_", case_sensitive=False, extra="ignore", ) # === Application === app_env: Literal["development", "staging", "production", "offline"] = "development" debug: bool = False log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO" host: str = "0.0.0.0" port: int = 8000 # === Database === # ⚠️ В production переопределите через ENV (не используйте default credentials) database_url: str = "postgresql+asyncpg://flowstack:flowstack@localhost:5432/flowstack" # === Redis === redis_url: str = "redis://localhost:6379/1" # === LLM Providers === llm_provider: Literal["openai", "anthropic", "ollama", "cloud_ru", "yandex", "auto"] = "auto" # Стандартные ключи openai_api_key: SecretStr | None = None anthropic_api_key: SecretStr | None = None ollama_base_url: str = "http://localhost:11434" # Cloud.ru (приоритетный провайдер) cloud_ru_base_url: str = "https://foundation-models.api.cloud.ru/v1" cloud_ru_api_key: SecretStr | None = None # Yandex Cloud yandex_base_url: str = "https://llm.api.cloud.yandex.net/foundationModels/v1" yandex_api_key: SecretStr | None = None # === Default models === default_model: str = "deepseek-ai/DeepSeek-V4-Pro" # ⚠️ Для Cloud.ru используйте совместимую embedding модель embedding_model: str = "text-embedding-3-small" # === RAG Service === rag_service_url: str = "http://localhost:8082" # === Limits === max_concurrent_runs: int = 10 max_tokens_per_run: int = 100_000 # === Timeouts (в секундах) === request_timeout: int = 300 llm_connect_timeout: int = 30 # Таймаут на установление соединения llm_read_timeout: int = 300 # Таймаут на чтение ответа от LLM (5 мин) llm_max_retries: int = 3 # === Security === # API key для аутентификации MCP JSON-RPC endpoint (tools.py) # ⚠️ Обязательно настройте в production (иначе MCP endpoint без auth) mcp_api_key: SecretStr | None = None # Shared secret для проверки что запросы приходят от Gateway (auth middleware) gateway_secret: SecretStr | None = None # === Paths === # ⚠️ Для production используйте постоянный путь (не /tmp) workspace_root: Path = Field(default=Path("/tmp/flowstack-workspaces")) # ======================================================================== # VALIDATION # ======================================================================== @model_validator(mode="after") def validate_config(self) -> Settings: """Предупредить если LLM провайдер не настроен.""" if self.app_env != "offline" and not self.is_configured(): warnings.warn( f"LLM провайдер '{self.llm_provider}' не настроен " f"(нет API ключа). Установите FLOWSTACK_CLOUD_RU_API_KEY " f"или другой ключ провайдера.", stacklevel=2, ) return self # ======================================================================== # PROPERTIES # ======================================================================== @property def timeout(self) -> int: """Alias для request_timeout (обратная совместимость).""" return self.request_timeout @property def resolved_provider(self) -> str: """ Реальный LLM провайдер. Для ``auto`` определяет провайдера по доступному ключу (приоритет: cloud_ru → yandex → openai → anthropic → ollama). """ if self.llm_provider != "auto": return self.llm_provider # auto: определить по доступному ключу (cloud_ru приоритет) if self.cloud_ru_api_key: return "cloud_ru" if self.yandex_api_key: return "yandex" if self.openai_api_key: return "openai" if self.anthropic_api_key: return "anthropic" if self.ollama_base_url: return "ollama" return "cloud_ru" # fallback по умолчанию @property def api_key(self) -> str | None: """ API ключ для текущего провайдера. ⚠️ Возвращает расшифрованное значение (get_secret_value) — НЕ логируйте его. Используйте только для передачи в LLM client. """ provider_keys = { "cloud_ru": self.cloud_ru_api_key, "yandex": self.yandex_api_key, "openai": self.openai_api_key, "anthropic": self.anthropic_api_key, } # Явный провайдер provider = self.resolved_provider if provider in provider_keys: key = provider_keys[provider] return key.get_secret_value() if key else None # Fallback: первый доступный ключ (cloud_ru приоритет) for key in ( self.cloud_ru_api_key, self.yandex_api_key, self.openai_api_key, self.anthropic_api_key, ): if key: return key.get_secret_value() return None @property def model(self) -> str: """Текущая модель для LLM запросов.""" return self.default_model @property def provider(self) -> str: """Текущий LLM провайдер (как настроен, без разрешения auto).""" return self.llm_provider @property def max_retries(self) -> int: """Максимум попыток для LLM запросов.""" return self.llm_max_retries # ======================================================================== # METHODS # ======================================================================== def is_configured(self) -> bool: """Проверить, настроен ли LLM провайдер.""" if self.resolved_provider == "ollama": return bool(self.ollama_base_url) return self.api_key is not None def get_base_url(self) -> str | None: """ Получить base URL для текущего провайдера. Returns: Base URL для OpenAI-compatible провайдеров, или None для openai/anthropic (SDK использует дефолтный URL). """ provider = self.resolved_provider if provider == "cloud_ru": return self.cloud_ru_base_url if provider == "yandex": return self.yandex_base_url if provider == "ollama": return f"{self.ollama_base_url}/v1" # openai/anthropic: None → SDK использует дефолтный URL return None @lru_cache(maxsize=1) def get_settings() -> Settings: """Получить кешированный экземпляр настроек.""" return Settings() # type: ignore[call-arg]