Complete DDS training workflow and delivery package

This commit is contained in:
andreysk0304 2026-09-24 01:10:49 +03:00
commit 4c4b91064f
229 changed files with 11969 additions and 1024 deletions

View file

@ -1,4 +1,4 @@
"""Кто играет звонящего: LLM, если есть ключ, иначе заготовки.
"""Кто играет звонящего: локальная LLM, таблица или заготовки.
Провайдер и модель меняются значением в конфиге, а не кодом. Заготовки —
не запасной костыль, а рабочий режим: занятие идёт и без сети.
@ -9,30 +9,45 @@ 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) -> Caller:
def build_caller(
scenario_id: str | None = None,
sessionmaker=None,
*,
use_pregenerated: bool = False,
) -> Caller:
settings = get_settings()
# Офлайн и «нет ключа» — это один и тот же путь: предгенерированная таблица,
# а не локальная модель. Формулировки в ней от облачной модели, а задержка
# нулевая (docs/arch/STACK.md).
if (settings.offline or not settings.llm_api_key) and scenario_id and has_table(scenario_id):
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)
if not settings.llm_api_key or settings.offline:
reason = "офлайн-режим" if settings.offline else "нет ключа LLM"
log.info("звонящий отвечает заготовками: %s (таблицы нет — make pregen)", reason)
return TemplateCaller()
client = LlmClient(sessionmaker=sessionmaker or _safe_sessionmaker())
log.info("звонящий на модели %s", settings.llm_model_caller)
return LlmCaller(client, model=settings.llm_model_caller)
log.info("звонящий отвечает фактами сценария: модель отключена или предгенерация не разрешена")
return TemplateCaller()
def _safe_sessionmaker():