862e6b7b25
Co-authored-by: Cursor <cursoragent@cursor.com>
185 lines
5.8 KiB
Python
185 lines
5.8 KiB
Python
"""Загрузка llm.config.yaml и переопределений из переменных окружения."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import yaml
|
|
from dotenv import load_dotenv
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
CONFIG_PATH = PROJECT_ROOT / "llm.config.yaml"
|
|
|
|
# .env в корне репозитория (секреты не коммитить)
|
|
load_dotenv(PROJECT_ROOT / ".env")
|
|
|
|
|
|
def _deep_get(data: dict[str, Any], *keys: str, default: Any = None) -> Any:
|
|
node: Any = data
|
|
for key in keys:
|
|
if not isinstance(node, dict):
|
|
return default
|
|
node = node.get(key)
|
|
if node is None:
|
|
return default
|
|
return node
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def load_config() -> dict[str, Any]:
|
|
if not CONFIG_PATH.exists():
|
|
return {}
|
|
with CONFIG_PATH.open(encoding="utf-8") as fh:
|
|
return yaml.safe_load(fh) or {}
|
|
|
|
|
|
class Settings:
|
|
"""Настройки интеграции LLM и временного локального RAG."""
|
|
|
|
@property
|
|
def project_root(self) -> Path:
|
|
return PROJECT_ROOT
|
|
|
|
@property
|
|
def llm_enabled(self) -> bool:
|
|
env = os.getenv("VKUS_LLM_ENABLED")
|
|
if env is not None:
|
|
return env.lower() in ("1", "true", "yes")
|
|
return bool(_deep_get(load_config(), "provider", "enabled", default=False))
|
|
|
|
@property
|
|
def llm_base_url(self) -> str:
|
|
# Полный URL из .env (OpenRouter и др.) — нормализуем до base
|
|
api_url = os.getenv("VKUS_LLM_API_URL")
|
|
if api_url:
|
|
url = api_url.rstrip("/")
|
|
if url.endswith("/v1/chat/completions"):
|
|
return url[: -len("/v1/chat/completions")]
|
|
return url
|
|
return os.getenv(
|
|
"VKUS_LLM_BASE_URL",
|
|
_deep_get(load_config(), "api", "base_url", default="https://apiai.sibur.local"),
|
|
)
|
|
|
|
@property
|
|
def llm_chat_url(self) -> str:
|
|
"""Полный URL для chat/completions."""
|
|
api_url = os.getenv("VKUS_LLM_API_URL")
|
|
if api_url:
|
|
return api_url.rstrip("/")
|
|
return f"{self.llm_base_url.rstrip('/')}/v1/chat/completions"
|
|
|
|
@property
|
|
def llm_model(self) -> str:
|
|
return os.getenv(
|
|
"VKUS_LLM_MODEL",
|
|
_deep_get(load_config(), "llm", "model", default="gpt-oss-120b"),
|
|
)
|
|
|
|
@property
|
|
def llm_api_key(self) -> str | None:
|
|
key_name = _deep_get(load_config(), "auth", "api_key_env", default="VKUS_LLM_API_KEY")
|
|
return os.getenv(key_name or "VKUS_LLM_API_KEY") or None
|
|
|
|
@property
|
|
def llm_jwt_token(self) -> str | None:
|
|
key_name = _deep_get(load_config(), "auth", "jwt_token_env", default="VKUS_LLM_JWT_TOKEN")
|
|
return os.getenv(key_name or "VKUS_LLM_JWT_TOKEN") or None
|
|
|
|
@property
|
|
def llm_timeout(self) -> float:
|
|
env = os.getenv("VKUS_LLM_TIMEOUT_SEC")
|
|
if env:
|
|
return float(env)
|
|
return float(_deep_get(load_config(), "api", "timeout_seconds", default=60))
|
|
|
|
@property
|
|
def llm_verify_ssl(self) -> bool:
|
|
return bool(_deep_get(load_config(), "api", "verify_ssl", default=True))
|
|
|
|
@property
|
|
def llm_temperature(self) -> float:
|
|
env = os.getenv("VKUS_LLM_TEMPERATURE")
|
|
if env:
|
|
return float(env)
|
|
return float(_deep_get(load_config(), "llm", "parameters", "temperature", default=0.3))
|
|
|
|
@property
|
|
def llm_max_tokens(self) -> int:
|
|
env = os.getenv("VKUS_LLM_MAX_TOKENS")
|
|
if env:
|
|
return int(env)
|
|
return int(_deep_get(load_config(), "llm", "parameters", "max_tokens", default=2048))
|
|
|
|
@property
|
|
def system_prompt_path(self) -> Path:
|
|
rel = _deep_get(
|
|
load_config(),
|
|
"llm",
|
|
"system_prompt",
|
|
"file",
|
|
default="Start data/Системный промпт - правила общения с пользователем.md",
|
|
)
|
|
return PROJECT_ROOT / str(rel)
|
|
|
|
@property
|
|
def system_prompt_fallback(self) -> str:
|
|
return str(
|
|
_deep_get(
|
|
load_config(),
|
|
"llm",
|
|
"system_prompt",
|
|
"fallback",
|
|
default="Ты — корпоративный ИТ-ассистент портала ВКУС.",
|
|
)
|
|
)
|
|
|
|
@property
|
|
def confidence_threshold(self) -> int:
|
|
return int(_deep_get(load_config(), "assistant", "confidence_threshold", default=70))
|
|
|
|
# --- Временный локальный RAG (внутри backend) ---
|
|
|
|
@property
|
|
def rag_mode(self) -> str:
|
|
"""local — временный RAG в backend; external — целевая платформа ИИ (будущее)."""
|
|
return str(_deep_get(load_config(), "rag", "mode", default="local"))
|
|
|
|
@property
|
|
def rag_local_enabled(self) -> bool:
|
|
env = os.getenv("VKUS_RAG_LOCAL_ENABLED")
|
|
if env is not None:
|
|
return env.lower() in ("1", "true", "yes")
|
|
return bool(_deep_get(load_config(), "rag", "local", "enabled", default=True))
|
|
|
|
@property
|
|
def rag_documents_path(self) -> Path:
|
|
rel = _deep_get(load_config(), "rag", "local", "documents_path", default="Manuals")
|
|
return PROJECT_ROOT / str(rel)
|
|
|
|
@property
|
|
def rag_top_k(self) -> int:
|
|
return int(_deep_get(load_config(), "rag", "local", "top_k", default=5))
|
|
|
|
@property
|
|
def rag_min_score(self) -> float:
|
|
return float(_deep_get(load_config(), "rag", "local", "min_score", default=0.12))
|
|
|
|
@property
|
|
def rag_chunk_size(self) -> int:
|
|
return int(_deep_get(load_config(), "rag", "local", "chunk_size", default=450))
|
|
|
|
@property
|
|
def use_pattern_kb(self) -> bool:
|
|
return bool(_deep_get(load_config(), "fallback", "use_local_knowledge_base", default=True))
|
|
|
|
@property
|
|
def use_template_on_llm_failure(self) -> bool:
|
|
return bool(_deep_get(load_config(), "fallback", "use_template_on_llm_failure", default=True))
|
|
|
|
|
|
settings = Settings()
|