build(client): use locked uv environment in launcher and Docker

This commit is contained in:
2026-10-06 18:27:09 -03:00
parent f8d5b7857a
commit fa00b36e49
16 changed files with 230 additions and 53 deletions
+5 -1
View File
@@ -10,8 +10,12 @@ the implementation at the recorded date; current operating instructions live in
### Changed
- Added a uv-managed Python 3.12 environment and Ruff, mypy, and pytest quality
checks. Split the FastAPI service into configuration, route, provider,
checks, all contained in `s2s-client/`. Startup and Docker use the committed
lockfile. Split the FastAPI service into configuration, route, provider,
context/reasoning, and realtime modules while retaining `server:app` startup.
- Added Pydantic response contracts with permissive upstream fields and original
payload serialization. Captured HTTP, request-schema, and realtime regression
fixtures preserve existing API behavior, including known failures.
- Removed the legacy browser site in favor of the React application in
`s2s-frontend`. FastAPI now serves only backend and documentation routes.
- Retired daily talk-time accounting and the associated Hugging Face account
+13 -5
View File
@@ -2,16 +2,24 @@
# Serve backend APIs, the WebSocket proxy, and shared project documentation.
# Run the React/Vite frontend separately; this image contains no browser UI.
# HF Spaces (sdk: docker) routes traffic to $PORT, default 7860.
FROM python:3.11-slim
FROM python:3.12.14-slim
COPY --from=ghcr.io/astral-sh/uv:0.12.5 /uv /uvx /bin/
ENV UV_LINK_MODE=copy \
UV_PYTHON_DOWNLOADS=never \
PATH="/app/s2s-client/.venv/bin:$PATH"
WORKDIR /app/s2s-client
COPY s2s-client/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY s2s-client/pyproject.toml s2s-client/uv.lock ./
RUN uv sync --locked --no-dev --no-install-project
COPY s2s-client/ .
COPY s2s-client/README.md s2s-client/server.py ./
COPY s2s-client/s2s_client/ ./s2s_client/
COPY docs/ /app/docs/
RUN uv sync --locked --no-dev
EXPOSE 7860
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "7860"]
CMD ["uvicorn", "s2s_client.main:app", "--host", "0.0.0.0", "--port", "7860"]
+10 -3
View File
@@ -31,17 +31,24 @@ Build from the repository root so the container includes the documentation:
docker build -f s2s-client/Dockerfile -t s2s-client .
```
Run the backend regression tests from the repository root after installing
`requirements.txt` in `.venv`:
The Python project is contained in this directory: `pyproject.toml`, `uv.lock`,
`.python-version`, `.venv`, quality tools, and tests. Use uv 0.12.5 and Python
3.12 (uv can provision Python). From the repository root:
```bash
.venv/bin/python -m unittest discover -s s2s-client/tests -v
make -C s2s-client install
make -C s2s-client check
```
Tests isolate environment configuration, use HTTP fixtures for allocation and
caller authentication, and start a loopback WebSocket service to verify audio
frames, keepalive, and teardown. No deployed services or database are needed.
See [backend development and compatibility](../docs/client/development.md) for
formatting, typing, schema policy, optional Git hooks, and deployment checks.
`uvicorn server:app --app-dir s2s-client` remains a supported entry point;
the implementation now lives in `s2s_client/`.
FastAPI's API explorer remains at `/docs`. Project documents are served as files below
`/docs/client/`, `/docs/speech/`, `/docs/adr/`, `/docs/examples/`, and
`/docs/proposals/`. The historical `/project/` and `/public/docs.html` URLs
-4
View File
@@ -1,4 +0,0 @@
fastapi==0.115.6
uvicorn[standard]==0.34.0
httpx==0.28.1
python-dotenv==1.2.2
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Build the actual runtime and check HTTP behavior without external services.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
IMAGE="s2s-client-smoke:$(date +%s)-$$"
container_id=""
cleanup() {
if [ -n "$container_id" ]; then
docker rm --force "$container_id" >/dev/null
fi
docker image rm "$IMAGE" >/dev/null 2>&1 || true
}
trap cleanup EXIT
docker build --file "$ROOT_DIR/s2s-client/Dockerfile" --tag "$IMAGE" "$ROOT_DIR"
container_id="$(docker run --detach --publish 127.0.0.1::7860 "$IMAGE")"
port="$(docker port "$container_id" 7860/tcp)"
ready=false
for ((attempt = 0; attempt < 60; attempt++)); do
if curl --noproxy '*' --silent --fail --max-time 1 "http://$port/api/config" >/dev/null; then
ready=true
break
fi
sleep 0.5
done
if [ "$ready" != true ]; then
docker logs "$container_id"
exit 1
fi
docker exec -i "$container_id" python - <<'PY'
import json
import urllib.error
import urllib.request
base = "http://127.0.0.1:7860"
with urllib.request.urlopen(base + "/api/config") as response:
assert json.load(response) == {
"search": False, "lb": False, "allowDirect": True, "s2sUrl": "",
"primaryPrompt": "", "reasoningType": "PROXY", "injectProfile": False,
"debugPrompt": False, "startSessionMode": "", "theme": "", "apipilot": False,
}
for path in ["/docs", "/docs/README.md", "/openapi.json"]:
with urllib.request.urlopen(base + path) as response:
assert response.status == 200
try:
urllib.request.urlopen(base + "/")
except urllib.error.HTTPError as error:
assert error.code == 404
else:
raise AssertionError("The backend root must remain 404")
print("Docker runtime smoke checks passed")
PY