"""LLM infrastructure (#960) — ОПЦИОНАЛЬНЫЙ слой поверх детерминированного движка. Foundational-пакет. Консьюмеры приедут позже: #956 граддок-extraction (sync Celery) и #957 chat (async FastAPI). Этот пакет их НЕ реализует — только инфраструктуру. ═══════════════════════════════════════════════════════════════════════════════ КОНТРАКТ БЕЗОПАСНОСТИ — обязателен к прочтению авторами консьюмеров ═══════════════════════════════════════════════════════════════════════════════ 1. Детерминированный fallback ВСЕГДА. ``complete`` НИКОГДА не бросает из-за LLM: при ``LLMResult.ok is False`` вы ОБЯЗАНЫ подставить свой детерминированный результат. Forecasting-движок остаётся детерминированным; LLM ничего не «ломает». res = complete(system_prompt=..., payload=safe) if not res.ok: return deterministic_result() # обязательно use(res.content, res.tool_calls) 2. Allowlist-first (§19 data-residency). Провайдер ВНЕШНИЙ (OpenAI) → данные покидают РФ. Поэтому в ``complete`` нельзя передать сырые строки/DB-записи — только ЯВНО собранный ``SafePayload`` из проверенных не-чувствительных полей: payload = SafePayload( text=public_regulation_text, # публичный/безопасный текст fields={"zone_index": "Ц-1", "floors": 16}, # агрегаты/коды is_confidential=insight.is_confidential, # прокинуть флаг источника! ) НЕ собирайте ``SafePayload`` из сырой строки insight/лида без проверки полей. 3. Hard-block конфиденциального. Если ``SafePayload.is_confidential is True`` — для внешнего провайдера ``complete`` НЕ отправит данные, вернёт fallback (reason="redaction_refused"). Всегда прокидывайте confidential-флаг источника. 4. Regex-scrub — вторичная защита (belt-and-suspenders): из ``text``/``fields`` автоматически вырезаются RU PII (телефон/email/СНИЛС/ИНН/паспорт/ФИО) перед отправкой. Это НЕ заменяет пункт 2 — не полагайтесь на scrub как на гарантию. 5. ``llm_enabled`` default False. В проде (до настройки секретов) сеть НЕ дёргается — ``complete`` сразу возвращает fallback. Включение — осознанное действие. """ from __future__ import annotations from .client import LLMResult, LLMUnavailableError, complete, complete_or_raise from .prompts import PromptNotFoundError, render from .provider import ( LLMProvider, LLMProviderError, LLMRateLimitedError, LLMTimeoutError, OpenAIProvider, ToolCall, ) from .redaction import RedactionRefusedError, SafePayload, scrub_safe_payload, scrub_text # Сортировка isort-style (RUF022). Группировка по слоям — в docstring выше: # orchestration (complete/complete_or_raise/LLMResult), redaction (SafePayload/...), # provider abstraction (LLMProvider/OpenAIProvider/... — шов под RU-hosted), prompts. __all__ = [ "LLMProvider", "LLMProviderError", "LLMRateLimitedError", "LLMResult", "LLMTimeoutError", "LLMUnavailableError", "OpenAIProvider", "PromptNotFoundError", "RedactionRefusedError", "SafePayload", "ToolCall", "complete", "complete_or_raise", "render", "scrub_safe_payload", "scrub_text", ]