feat: один прод-compose со всеми моделями из готовых образов
This commit is contained in:
parent
c3fc1cec79
commit
a694702d8f
44 changed files with 1126 additions and 2075 deletions
|
|
@ -2,5 +2,12 @@
|
|||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
models/
|
||||
tests/
|
||||
# Образ публикуется в registry: записи звонков, дампы БД и локальные ключи
|
||||
# разработчика в него попасть не должны.
|
||||
recordings/
|
||||
backups/
|
||||
.env
|
||||
.env.*
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.11-slim
|
||||
FROM python:3.11-slim AS base
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
||||
|
||||
|
|
@ -12,11 +12,32 @@ RUN apt-get update && apt-get install -y --no-install-recommends fonts-dejavu-co
|
|||
|
||||
# Зависимости — из pyproject.toml, а не отдельным списком: ручной список разошёлся
|
||||
# с проектом, и бэкенд в контейнере упал на первом же новом пакете (num2words).
|
||||
# Голосовой контур (группа voice: torch, onnx-asr) в образ не входит: под WSL
|
||||
# модели запускаются нативно — так они и мерялись (docs/LATENCY.md).
|
||||
# Голосовой контур (группа voice: torch, onnx-asr) в стадию base не входит —
|
||||
# его вместе с весами добавляет стадия voice ниже.
|
||||
COPY pyproject.toml ./
|
||||
RUN uv pip install --system -r pyproject.toml
|
||||
|
||||
COPY . .
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
# Прод-образ стенда: голосовой контур, сценарии и веса внутри, bind-mount'ов нет.
|
||||
# Веса и сценарии приходят именованными контекстами сборки (docker-bake.hcl,
|
||||
# docker-compose.yml): models=backend/models, scenarios=scenarios,
|
||||
# licenses=deploy/licenses. В Dockerfile ничего не скачивается — хост Silero
|
||||
# ненадёжен; нет файла — COPY падает с его именем, `make images` проверяет заранее.
|
||||
# Whisper в образ не входит: единственный проверенный вживую STT — GigaAM.
|
||||
FROM base AS voice
|
||||
# torch только CPU-сборка: обычный PyPI тянет CUDA на гигабайты (pyproject).
|
||||
RUN uv pip install --system --extra-index-url https://download.pytorch.org/whl/cpu \
|
||||
'torch>=2.2' 'onnx-asr>=0.6' 'scipy>=1.11'
|
||||
COPY --from=scenarios . /scenarios
|
||||
# Из GigaAM — только RNNT int8: CTC-вариант стенд не загружает.
|
||||
COPY --from=models gigaam-v3-onnx/config.json gigaam-v3-onnx/v3_vocab.txt \
|
||||
gigaam-v3-onnx/v3_rnnt_encoder.int8.onnx gigaam-v3-onnx/v3_rnnt_decoder.int8.onnx \
|
||||
gigaam-v3-onnx/v3_rnnt_joint.int8.onnx /app/models/gigaam-v3-onnx/
|
||||
COPY --from=models silero-vad/silero_vad.onnx /app/models/silero-vad/
|
||||
COPY --from=models silero-tts/v5_ru.pt /app/models/silero-tts/
|
||||
COPY --from=models e5-small/config.json e5-small/model_quantized.onnx e5-small/tokenizer.json \
|
||||
/app/models/e5-small/
|
||||
COPY --from=licenses . /licenses
|
||||
|
|
|
|||
|
|
@ -57,33 +57,18 @@ def test_websocket_origin_policy(headers, scope, expected):
|
|||
|
||||
def test_nginx_proxies_preserve_external_host_for_websocket_origin_validation():
|
||||
project_root = Path(__file__).resolve().parents[2]
|
||||
for config in ("nginx.conf.template", "nginx.tls.conf.template"):
|
||||
text = (project_root / "frontend" / config).read_text(encoding="utf-8")
|
||||
match = re.search(r"location /ws/ \{(.*?)^ \}", text, re.MULTILINE | re.DOTALL)
|
||||
assert match is not None, f"{config}: missing WebSocket proxy block"
|
||||
websocket_location = match.group(1)
|
||||
assert "proxy_set_header X-Forwarded-Host $http_host;" in websocket_location
|
||||
tls = (project_root / "frontend" / "nginx.tls.conf.template").read_text(encoding="utf-8")
|
||||
match = re.search(r"location /ws/ \{(.*?)^ \}", tls, re.MULTILINE | re.DOTALL)
|
||||
assert match is not None
|
||||
tls_websocket_location = match.group(1)
|
||||
assert "proxy_set_header X-Forwarded-Proto https;" in tls_websocket_location
|
||||
text = (project_root / "frontend" / "nginx.conf.template").read_text(encoding="utf-8")
|
||||
match = re.search(r"location /ws/ \{(.*?)^ \}", text, re.MULTILINE | re.DOTALL)
|
||||
assert match is not None, "missing WebSocket proxy block"
|
||||
assert "proxy_set_header X-Forwarded-Host $http_host;" in match.group(1)
|
||||
|
||||
|
||||
def test_cluster_nginx_pins_all_session_channels_and_session_apis_to_one_hash_key():
|
||||
def test_nginx_routes_webrtc_phone_to_asterisk():
|
||||
project_root = Path(__file__).resolve().parents[2]
|
||||
for config in ("nginx.cluster.conf.template", "nginx.cluster.tls.conf.template"):
|
||||
text = (project_root / "frontend" / config).read_text(encoding="utf-8")
|
||||
assert "hash $session_route_key consistent;" in text
|
||||
assert "server backend:8000" in text and "server backend-b:8000" in text
|
||||
assert re.search(
|
||||
r"~\^/ws/\(\?:control\|call\|observe\|station\)/\(\[0-9a-fA-F-\]\{36\}\)",
|
||||
text,
|
||||
), f"{config}: all WebSocket channels must extract the same session UUID"
|
||||
assert re.search(
|
||||
r"~\^/api/sessions/\(\[0-9a-fA-F-\]\{36\}\)", text
|
||||
), f"{config}: session REST endpoints must use the same routing key"
|
||||
assert text.count("proxy_pass http://backend_cluster;") == 2
|
||||
text = (project_root / "frontend" / "nginx.conf.template").read_text(encoding="utf-8")
|
||||
match = re.search(r"location = /sip-ws \{(.*?)^ \}", text, re.MULTILINE | re.DOTALL)
|
||||
assert match is not None, "WebRTC-телефон ходит на same-origin /sip-ws"
|
||||
assert "proxy_pass http://$sip_backend:8088/ws;" in match.group(1)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
|||
Loading…
Reference in a new issue