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
+1
View File
@@ -8,6 +8,7 @@ do not define the current implementation.
- [Usage and configuration](client/usage.md)
- [Architecture and source map](client/architecture.md)
- [Backend development and compatibility](client/development.md)
- [Reasoning modes](client/reasoning.md)
- [Context injection](client/context-injection.md)
- [Tool calling](client/tool-calling.md)
+12 -1
View File
@@ -42,7 +42,14 @@ audio. It is not an unconditional byte-for-byte tunnel.
| Source | Responsibility |
|---|---|
| [server.py](../../s2s-client/server.py) | Configuration, documentation serving, WebSocket relay, context loading, ApiPilot, reasoning, search and queue routes |
| [server.py](../../s2s-client/server.py) | Compatibility entry point for existing `server:app` commands |
| [main.py](../../s2s-client/s2s_client/main.py) | FastAPI assembly, documentation serving and redirects |
| [api](../../s2s-client/s2s_client/api) | HTTP route registration, handlers and compatible response validation |
| [models](../../s2s-client/s2s_client/models) | Pydantic request and response contracts |
| [core](../../s2s-client/s2s_client/core) | Existing environment parsing, logging, URL helpers and diagnostics |
| [providers](../../s2s-client/s2s_client/providers) | Caller context and ApiPilot HTTP integrations |
| [services](../../s2s-client/s2s_client/services) | Context construction, reasoning and tool-context rendering |
| [realtime](../../s2s-client/s2s_client/realtime) | WebSocket relay, events, markers and audio handling |
| [start_ui.sh](../../start_ui.sh) | Dependency setup, ports, readiness, reload processes, and shutdown |
| [vite.config.ts](../../s2s-frontend/vite.config.ts) | Public development endpoint, HMR, allowed hosts, and backend routing |
| [App.tsx](../../s2s-frontend/src/App.tsx) | React layout, voice controls, dialogs, profile and reports sidebar |
@@ -75,6 +82,10 @@ source text, and directories without `index.html` are not browsable pages.
FastAPI serves backend and documentation routes only. The frontend source and
assets live in `s2s-frontend` and are served by Vite.
The uv project and its Python tools are scoped to `s2s-client/`. Response models
describe service-owned structure and retain loose upstream values. See
[development and compatibility](development.md) before tightening a contract.
## Audio path
1. The browser captures microphone audio with echo cancellation, noise
+2 -1
View File
@@ -2,7 +2,8 @@
Context reaches the assistant through browser-built instructions, server-patched
realtime session instructions, and HTTP reasoning context. The implementation is
in [server.py](../../s2s-client/server.py),
in the server's [context services](../../s2s-client/s2s_client/services/context.py),
[realtime proxy](../../s2s-client/s2s_client/realtime/proxy.py),
[context.ts](../../s2s-frontend/src/lib/context.ts), and the
[WebSocket client](../../s2s-frontend/src/realtime/client.ts).
+77
View File
@@ -0,0 +1,77 @@
# Backend development and compatibility
The FastAPI project lives entirely in `s2s-client/`. It uses uv 0.12.5, Python
3.12, and a committed `uv.lock`. The frontend and speech pipeline keep their
own tooling. Runtime dependency versions match the environment used to capture
the pre-migration server; framework upgrades require a separate compatibility
review.
## Commands
From the repository root:
```bash
make -C s2s-client install # uv sync --locked, including development tools
make -C s2s-client format # Ruff safe fixes and formatting
make -C s2s-client check # lock freshness, format, lint, mypy, pytest
make -C s2s-client test-launcher # Bash supervisor integration fixtures
make -C s2s-client test-image # Docker build and isolated runtime smoke test
```
Run `uv lock` inside `s2s-client/` when intentionally changing dependencies.
Do not install server packages into a shared root environment. The launcher
syncs `s2s-client/.venv` and retains root `.env` loading, port defaults,
readiness checks, and process-group shutdown. Offline starts require the
locked packages and Python runtime to have been installed or cached already.
Optional hooks use `make -C s2s-client hooks-install`; the configuration stays
in `s2s-client/.pre-commit-config.yaml`. `make -C s2s-client hooks-run` runs the
same hooks without installing them. Hooks and Python quality checks apply
only to the client service. The server entry points are `s2s_client.main:app`
and the retained `server:app` shim; use `--app-dir s2s-client` from the root.
## Schema boundaries
`models/requests.py` retains the existing search, reasoning, and ApiPilot
request models, including their defaults, coercion, and ignored extra fields.
The queue beacon still accepts malformed JSON and non-object bodies as before;
its Pydantic view is applied only after the original object check, and
`queueId` remains unconstrained.
`models/responses.py` validates structure constructed by this server. Fields
owned by external services remain `Any` where the old parser imposed no type
constraint. Object models allow extra fields. Caller profiles can be arbitrary
JSON; load-balancer fields remain optional; execution results and error details
remain open. Existing provider parsing, authentication precedence, argument
filtering, environment parsing, and WebSocket frames are unchanged.
HTTP handlers call `validated_json_response`: Pydantic validates the payload,
then `JSONResponse` serializes the original object. Returning a dumped model
would insert defaults or change values/key order, so do not replace this helper
with model serialization without a contract review. Route `response_model`
declarations publish the new response schemas in OpenAPI. Existing request
schemas and validation errors remain unchanged. HTTP exceptions retain their
existing error shapes; there is no new common error envelope.
## Compatibility evidence
`tests/fixtures/legacy-contracts.json` was captured from the original monolithic
server at commit `3d42df5`, before refactoring. Tests compare 86 HTTP scenarios,
including exact response text, statuses, selected headers, and outgoing mock
HTTP requests. They also compare request OpenAPI definitions and representative
realtime frames. Additional tests cover response validation and permissive
upstream values; the original local HTTP/WebSocket tests remain in the suite.
The captures deliberately preserve unknown fields, omitted fields, unusual JSON
values, queue beacon edge cases, permissive tool arguments, and known failures.
Examples include reasoning's existing uninitialized local variables in certain
configuration combinations, API-key-only tool precheck behavior, the catalog
cache's existing key, and relay termination after a suppressed invalid
`session.update` error. Fix those in separately reviewed behavior changes;
do not regenerate golden captures to make a refactor pass.
These checks use local fixtures and do not claim exhaustive coverage of all
provider responses, live speech models, or production configuration. No deployed
services or real credentials are needed. The Docker check verifies the actual
runtime image, API configuration, documentation, OpenAPI, and the backend's
existing root 404; it removes its temporary container and image afterward.
+1 -1
View File
@@ -99,7 +99,7 @@ realtime backend instead.
|---|---|
| Change the base agent prompt | `S2S_PRIMARY_PROMPT` or browser settings when unpinned |
| Change browser context assembly | `buildInstructions()` in `s2s-frontend/src/lib/context.ts` |
| Change HTTP provider messages | `reasoning()` in `server.py` |
| Change HTTP provider messages | `reasoning()` in `s2s-client/s2s_client/services/reasoning.py` |
| Change external-answer rendering | `VoiceController.reason()` and `RealtimeClient.renderExternalAnswer()` |
| Change Node-RED processing | `s2s-reasoning-engine/flows.json` |
| Change native model/tool generation | `speech_to_speech/LLM` and realtime handlers |
+5 -4
View File
@@ -35,9 +35,10 @@ launcher. If the page loads but edits do not appear, check the browser's
`/__vite_hmr` WebSocket and the reverse proxy's upgrade handling. Speech uses
the separate `/v1/realtime` WebSocket on the same public origin.
For a startup dependency error, check Node/npm on the process manager's PATH,
Python venv support, and `setsid`/`curl`. The first start installs dependencies;
later starts reuse them until frontend manifests change. Changes to root
For a startup dependency error, check uv 0.12.5 and Node/npm on the process
manager's PATH, plus `setsid`/`curl`. uv manages Python 3.12 in
`s2s-client/.venv` and checks the committed lock on every start. Frontend
dependencies are reused until their manifests change. Changes to root
`.env` or launcher settings require a restart.
## The assistant promises a tool but nothing happens
@@ -126,7 +127,7 @@ From the repository root, run the frontend checks and local backend fixtures:
```bash
npm --prefix s2s-frontend run check
npm --prefix s2s-frontend run build
.venv/bin/python -m unittest discover -s s2s-client/tests -v
make -C s2s-client check
```
These checks cover frontend contracts and local session/queue/WebSocket
+9 -6
View File
@@ -12,8 +12,8 @@ From the repository root:
S2S_PROXY_TARGET=ws://localhost:8765/v1/realtime ./start_ui.sh
```
The launcher loads the root `.env`, creates `.venv` if needed, installs the
client requirements when the dependency check fails, and installs the locked
The launcher loads the root `.env`, syncs `s2s-client/.venv` from the committed
`s2s-client/uv.lock`, and installs the locked
frontend dependencies when needed. Vite serves the React frontend on port
8420 and proxies the backend routes to Uvicorn on `127.0.0.1:8421`.
`UI_PORT` overrides the public port; `S2S_BACKEND_PORT` overrides the internal
@@ -21,7 +21,7 @@ port. Both servers reload source edits, and stopping the launcher stops both.
The speech pipeline remains a separate service. Because `.env` is sourced by
the script, values in that file can replace variables supplied on the command line.
Use Node.js 22.18+ on the 22.x line or Node.js 24+, npm, Python 3 with venv,
Use Node.js 22.18+ on the 22.x line or Node.js 24+, npm, uv 0.12.5,
Bash 4.4+, `setsid`, and `curl`. Remote staging access also needs the exact
hostname in `S2S_DEV_ALLOWED_HOSTS`, HTTPS, and WebSocket forwarding for speech
and Vite HMR. See [frontend development setup](../../s2s-frontend/README.md).
@@ -29,15 +29,18 @@ and Vite HMR. See [frontend development setup](../../s2s-frontend/README.md).
To install and start the backend manually:
```bash
python3 -m venv .venv
.venv/bin/python -m pip install -r s2s-client/requirements.txt
uv sync --project s2s-client --locked
S2S_PROXY_TARGET=ws://localhost:8765/v1/realtime \
.venv/bin/python -m uvicorn --app-dir s2s-client server:app \
uv run --project s2s-client --locked uvicorn --app-dir s2s-client s2s_client.main:app \
--host 127.0.0.1 --port 8421 --reload --reload-dir s2s-client
```
Then, in a second terminal, run `cd s2s-frontend`, `npm ci`, and `npm run dev`.
uv provisions Python 3.12 when needed. The Python environment belongs only to
`s2s-client`; an existing root `.venv` for speech dependencies is left alone.
See [development and compatibility](development.md) for checks and schema rules.
Open <http://localhost:8420/>, tap the orb/microphone control, grant microphone
access, and speak. Microphone access requires HTTPS or a local browser origin;
use HTTPS when accessing the UI from another machine.