CI / ruff · pytest (pull_request) Successful in 2m15s
OpenRouter returns 403 «Access denied by security policy» from the RF server, and parsing that string error crashed the request (500 instead of the fallback answer). Error parsing is robust now and any agent exception degrades to the extractive answer. GigaChat tool provider (GigaChat-2-Pro by default) converts the agent's OpenAI-style history to function_call / role=function; auto mode prefers GigaChat when credentials are set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.5 KiB
Python
58 lines
2.5 KiB
Python
"""Typed application settings. The only place that reads environment variables."""
|
|
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
from typing import Literal
|
|
|
|
from pydantic import Field
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
ROOT_DIR = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
# Empty values in .env (e.g. `TG_BOT_TOKEN=`) fall back to defaults instead of failing validation.
|
|
model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_ignore_empty=True)
|
|
|
|
# Two separate databases (ADR-003): identity holds direct identifiers, clinical holds pseudonymised data.
|
|
identity_db_url: str = "postgresql+asyncpg://identity_app:identity_app@localhost:5432/identity"
|
|
clinical_db_url: str = "postgresql+asyncpg://clinical_app:clinical_app@localhost:5432/clinical"
|
|
|
|
# HMAC key linking identity and clinical records (hex, 32+ bytes).
|
|
pseudo_key: str = Field(default="dev-only-insecure-key-change-me-0000000000000000", min_length=32)
|
|
|
|
# LLM: GigaChat only (152-FZ: no foreign APIs). `mock` is the offline default (ADR-004).
|
|
llm_provider: Literal["mock", "gigachat"] = "mock"
|
|
gigachat_credentials: str = ""
|
|
gigachat_scope: str = "GIGACHAT_API_PERS"
|
|
gigachat_model: str = "GigaChat"
|
|
gigachat_verify_ssl: bool = False
|
|
|
|
# Chat agent (tool-using LLM over RAG + patient record). `auto` = OpenRouter if a key is set, else off
|
|
# (extractive RAG / instruction answers stay). OpenRouter is a TEMPORARY prototype provider on synthetic
|
|
# data only — prod must use GigaChat (152-FZ, foreign services are forbidden for real PII).
|
|
chat_agent: Literal["auto", "off", "mock", "openrouter", "gigachat"] = "auto"
|
|
openrouter_api_key: str = ""
|
|
openrouter_model: str = "google/gemini-2.5-flash-lite"
|
|
openrouter_base_url: str = "https://openrouter.ai/api/v1"
|
|
gigachat_agent_model: str = "GigaChat-2-Pro" # function calling quality matters more than price here
|
|
chat_agent_timeout_s: float = 25.0
|
|
chat_agent_max_steps: int = 5
|
|
|
|
# Telegram is used only for the prototype on synthetic data; prod channel is MAX / Gosuslugi.
|
|
tg_bot_token: str = ""
|
|
doctor_notify_chat_id: int | None = None
|
|
|
|
# Virtual clock for the demo (ADR-005).
|
|
demo_clock: bool = True
|
|
tz: str = "Europe/Moscow"
|
|
|
|
api_base_url: str = "http://localhost:8000"
|
|
config_dir: Path = ROOT_DIR / "config"
|
|
data_dir: Path = ROOT_DIR / "data"
|
|
|
|
|
|
@lru_cache
|
|
def get_settings() -> Settings:
|
|
return Settings()
|