lct-hack/backend/app/dialog/factory.py
2026-09-24 01:10:49 +03:00

59 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Кто играет звонящего: локальная LLM, таблица или заготовки.
Провайдер и модель меняются значением в конфиге, а не кодом. Заготовки —
не запасной костыль, а рабочий режим: занятие идёт и без сети.
"""
import logging
from app.config import get_settings
from app.dialog.caller import Caller, LlmCaller, TemplateCaller
from app.dialog.llm import LlmClient
from app.dialog.llm import is_loopback_url
from app.dialog.tree import TreeCaller, has_table
from app.db.base import get_sessionmaker
log = logging.getLogger(__name__)
def build_caller(
scenario_id: str | None = None,
sessionmaker=None,
*,
use_pregenerated: bool = False,
) -> Caller:
settings = get_settings()
local = settings.llm_provider == "local"
mode = settings.dialogue_model_mode
if mode not in {"dialogue", "russian_control"}:
raise ValueError(f"неизвестный DIALOGUE_MODEL_MODE: {mode}")
model = settings.llm_model_control if mode == "russian_control" else settings.llm_model_caller
base_url = settings.llm_control_base_url if mode == "russian_control" else settings.llm_base_url
model_allowed = settings.llm_provider != "disabled" and bool(model and base_url and (
is_loopback_url(base_url, allow_docker_host=settings.allow_docker_host_models)
if settings.offline or local else settings.llm_api_key
))
# Локальный loopback не считается внешней сетью. Если сервер модели не
# отвечает, LlmCaller откатится на проверенные заготовки этой же реплики.
if model_allowed:
client = LlmClient(sessionmaker=sessionmaker or _safe_sessionmaker(), base_url=base_url)
log.info("звонящий: режим %s, модель %s, адрес %s", mode, model, base_url)
return LlmCaller(client, model=model)
if use_pregenerated and scenario_id and has_table(scenario_id):
log.info("звонящий по предгенерированной таблице сценария %s", scenario_id)
return TreeCaller(scenario_id)
log.info("звонящий отвечает фактами сценария: модель отключена или предгенерация не разрешена")
return TemplateCaller()
def _safe_sessionmaker():
"""Кэш в Postgres — приятный бонус, а не условие работы звонящего."""
try:
return get_sessionmaker()
except Exception: # noqa: BLE001
log.warning("кэш LLM выключен: база недоступна")
return None