From 305951fc680f64b7332c05889bf95dfb0beb3748 Mon Sep 17 00:00:00 2001 From: gglamer Date: Mon, 28 Sep 2026 21:14:38 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81?= =?UTF-8?q?=D1=8B=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20compose?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=BF=D1=83=D1=81=D0=BA=D0=B0=D1=8E=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20=D0=BE=D1=84=D0=BB=D0=B0=D0=B9=D0=BD-=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=BE=D0=B9=20=D0=B0=D0=B4?= =?UTF-8?q?=D1=80=D0=B5=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/dialog/llm.py | 7 ++++++- backend/tests/test_local_models.py | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/app/dialog/llm.py b/backend/app/dialog/llm.py index fa76a8b..69eca90 100644 --- a/backend/app/dialog/llm.py +++ b/backend/app/dialog/llm.py @@ -24,6 +24,11 @@ from app.db.models import LlmCache log = logging.getLogger(__name__) +#: Имена сервисов моделей из корневого docker-compose.yml. Однословное имя +#: разрешает только DNS внутренней сети Compose, наружу оно не уходит. +DOCKER_MODEL_HOSTS = frozenset({"host.docker.internal", "llm-qwen", "llm-vikhr"}) + + class LlmUnavailable(RuntimeError): """Сеть, ключ или провайдер отказали. Звонящий откатывается на заготовки, занятие продолжается — молчащий звонящий хуже шаблонной фразы.""" @@ -40,7 +45,7 @@ def is_loopback_url(value: str, *, allow_docker_host: bool = False) -> bool: try: local_host = ip_address(host).is_loopback except ValueError: - local_host = allow_docker_host and host == "host.docker.internal" + local_host = allow_docker_host and host in DOCKER_MODEL_HOSTS return (url.scheme == "http" and local_host and url.port is not None and not url.username and not url.password) except (ValueError, TypeError): diff --git a/backend/tests/test_local_models.py b/backend/tests/test_local_models.py index dd823fa..4205da5 100644 --- a/backend/tests/test_local_models.py +++ b/backend/tests/test_local_models.py @@ -43,6 +43,14 @@ def test_offline_model_address_must_be_literal_loopback(): ) +def test_compose_model_services_need_the_docker_flag(): + for url in ("http://llm-qwen:18080/v1", "http://llm-vikhr:18081/v1"): + assert not is_loopback_url(url) + assert is_loopback_url(url, allow_docker_host=True) + assert not is_loopback_url("http://llm-qwen.evil.test:18080/v1", allow_docker_host=True) + assert not is_loopback_url("http://llm-other:18080/v1", allow_docker_host=True) + + @pytest.mark.asyncio @pytest.mark.parametrize("api_key", ["", "leftover-cloud-key"]) async def test_local_llm_never_sends_an_api_key(monkeypatch, api_key):