gendesign/backend/tests/services/llm/test_config.py
Light1YT 4af7ba5a40
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / backend-tests (push) Successful in 6m25s
CI / backend-tests (pull_request) Successful in 6m20s
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m38s
Deploy / build-worker (push) Successful in 2m42s
Deploy / deploy (push) Successful in 1m13s
CI / changes (push) Successful in 5s
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
feat(llm): foundational LLM infra package with §19 redaction + deterministic fallback (#960)
Optional external-OpenAI layer over the deterministic forecasting engine. Gated by
llm_enabled (default False) so prod makes no network calls until deliberately enabled.
Allowlist-first SafePayload contract + is_confidential hard-block + RU-PII regex scrub
(mandatory on the external path). Abstract LLMProvider seam (is_external) for a future
RU-hosted provider. Sync httpx core (Celery-friendly); tool/function-calling pass-through;
timeout + bounded 429/5xx retry + per-request call cap, all degrading to fallback.
Raw httpx (no openai SDK -> no pyproject/lock drift). 47 tests, ruff + mypy clean.

Refs #960
2026-06-08 15:44:16 +05:00

40 lines
1.8 KiB
Python

"""Config defaults для LLM-инфраструктуры (#960).
Критично: llm_enabled СТРОГО False по умолчанию, ключ None. Это гарантирует, что
прод не позвонит в OpenAI до осознанного включения.
"""
from __future__ import annotations
from app.core.config import Settings
def test_llm_disabled_by_default() -> None:
"""llm_enabled default False — прод не делает сетевых вызовов случайно."""
s = Settings(_env_file=None) # type: ignore[call-arg] # игнор .env при проверке дефолтов
assert s.llm_enabled is False
def test_openai_api_key_none_by_default() -> None:
"""Ключ не зашит — None по умолчанию (читается только из env OPENAI_API_KEY)."""
s = Settings(_env_file=None) # type: ignore[call-arg]
assert s.openai_api_key is None
def test_llm_safe_defaults() -> None:
"""Прочие дефолты безопасны: внешний base_url, конечные таймаут/лимиты, cap=None."""
s = Settings(_env_file=None) # type: ignore[call-arg]
assert s.llm_model == "gpt-4o-mini"
assert s.llm_base_url == "https://api.openai.com/v1"
assert s.llm_timeout_s > 0
assert s.llm_max_output_tokens > 0
assert s.llm_max_calls_per_request >= 1
assert s.llm_daily_cost_cap_usd is None
assert s.llm_max_retries >= 0
def test_openai_api_key_reads_from_env(monkeypatch) -> None:
"""OPENAI_API_KEY из окружения мапится в settings.openai_api_key (fake-ключ)."""
monkeypatch.setenv("OPENAI_API_KEY", "test-fake-key-not-real")
s = Settings(_env_file=None) # type: ignore[call-arg]
assert s.openai_api_key == "test-fake-key-not-real"