110 lines
6.9 KiB
Markdown
110 lines
6.9 KiB
Markdown
# Project Design Requirements: phproxy
|
|
|
|
Status: initial implementation
|
|
|
|
## Purpose
|
|
|
|
Provide a small single-host service that routes image-generation requests from installation clients to configured Google Gemini or OpenRouter API keys. It must tolerate long upstream response times, multiple reference images, client disconnects in polling mode, and upstream key/provider failures.
|
|
|
|
## Users and operating model
|
|
|
|
- Photobooth/installation clients submit a prompt, model, optional parameters, and zero or more images.
|
|
- An administrator provisions clients and manages access.
|
|
- Every client must have a secret. Generation, job polling, and client-managed key operations require that secret.
|
|
- The initial service runs as one Uvicorn process against a local SQLite database on persistent disk.
|
|
|
|
## Requirements
|
|
|
|
### Functional
|
|
|
|
1. Accept multipart image-generation requests at `POST /v1/generate`.
|
|
2. Support direct long-hold responses and `async_mode=true` polling jobs.
|
|
3. Support Google native Gemini and OpenRouter adapters, including multiple uploaded images and normalized image/text outputs.
|
|
4. Resolve named model aliases from configuration and pass through explicit model IDs.
|
|
5. Store multiple provider keys per client and try keys by priority, preferring the requested provider first.
|
|
6. Retry another configured key/provider for rate-limit, server, and connection/timeout failures. Do not retry malformed or rejected requests as if a provider were unavailable.
|
|
7. Persist clients, provider keys, jobs, and job results in SQLite WAL mode.
|
|
8. Provide a minimal browser UI and admin/client key-management API.
|
|
9. Return upstream-reported usage and per-generation USD cost when the provider supplies it.
|
|
10. Record request intake, upstream, and total execution time, provider/model selection, failures, image count/bytes, and reported cost. Show recent records and aggregates in the admin UI.
|
|
|
|
### Non-functional and security
|
|
|
|
- Use async HTTP for all upstream network I/O and a shared bounded connection pool.
|
|
- Bound aggregate uploaded bytes, image count, and decoded pixel dimensions. Accept only valid JPEG, PNG, and WebP images; re-encode before forwarding to strip metadata and trailing payloads.
|
|
- Require an explicit administrator secret at startup; never ship a default secret.
|
|
- Store client secrets hashed. Store provider keys only because they are needed for upstream requests; restrict database file access and redact keys from API responses.
|
|
- Scope job reads and client key operations to the authenticated owning client.
|
|
- Never return upstream response bodies or internal exception traces to callers.
|
|
- Keep polling job ownership in the database. A process restart must not leave a job forever marked as running.
|
|
- Use a single app worker with local SQLite in this release. Horizontal scaling and durable background execution are out of scope.
|
|
|
|
## API contracts
|
|
|
|
### Generate
|
|
|
|
`POST /v1/generate` multipart fields:
|
|
|
|
- `client_id` (required)
|
|
- `prompt` (optional, defaults empty)
|
|
- `model` (optional, defaults `nano-banana`)
|
|
- `provider` (optional: `google` or `openrouter`)
|
|
- `params` (optional JSON object string)
|
|
- `images` (repeated image upload fields)
|
|
- `x-client-secret` header (required)
|
|
|
|
Success is `200` with `provider_used`, `model_used`, `images`, `text`, elapsed time, and failover attempt count. With `?async_mode=true`, success is `200` with `job_id`, status, and poll URL.
|
|
|
|
### Poll
|
|
|
|
`GET /v1/jobs/{job_id}?client_id={client_id}` plus the client's secret header. Returns status and, when complete, result; terminal failures include a sanitized error. Unknown and cross-client job IDs return `404`.
|
|
|
|
### Management
|
|
|
|
- Admin routes use `x-admin-secret`.
|
|
- Key create/delete uses `auth-secret`, accepted only for the administrator or the owning client.
|
|
- Admin state returns only a short key prefix and never a full key.
|
|
|
|
Validation errors use FastAPI's `detail` format; upstream failures return `502` with sanitized attempt metadata.
|
|
|
|
## Data model
|
|
|
|
- `clients`: client ID primary key, name, required client-secret hash, activation flag, creation timestamp.
|
|
- `provider_keys`: owned by a client with cascading deletion; provider constrained to `google`/`openrouter`; key, priority, activation and failure metadata.
|
|
- `jobs`: owned by a client with cascading deletion; status, serialized request/result/error and timestamps. On startup, interrupted jobs are marked failed.
|
|
|
|
SQLite connections enable foreign keys and busy timeout; WAL is initialized once the DB opens. Provider-route and job-owner lookups are indexed. The DB and backups contain upstream credentials and potentially image data in queued job payloads/results, so filesystem and backup permissions are part of deployment security.
|
|
|
|
## Architecture and flow
|
|
|
|
1. FastAPI validates the multipart request and client authentication.
|
|
2. Uploaded images are bounded, decoded, re-encoded to an allowlisted raster format, then encoded for the provider's JSON API.
|
|
3. A route loads the active client's provider keys from SQLite.
|
|
4. The adapter translates the request to Google or OpenRouter format and sends through shared async `httpx`.
|
|
5. A retryable upstream failure advances to the next configured key; success is normalized into a provider-neutral response.
|
|
6. Polling mode persists a queued job before scheduling the single-process async worker; state and results are persisted.
|
|
|
|
## Operational plan
|
|
|
|
1. Install Python dependencies and set a unique `ADMIN_SECRET`.
|
|
2. Mount a persistent local volume at the configured database path with restrictive permissions.
|
|
3. Run one Uvicorn worker as `www-data`, bound to `127.0.0.1:8081`. Put TLS and request-size enforcement at Nginx; keep the application port private.
|
|
4. Create clients, add provider credentials, then call `/healthz` and verify one small generation using the intended account/model.
|
|
5. Back up the database securely. Treat backups and job records as secrets and apply retention appropriate to the installation.
|
|
|
|
## Delivery plan
|
|
|
|
- [x] Define requirements, API, data model, constraints, and operational plan.
|
|
- [x] Add runnable FastAPI service, SQLite persistence, adapters, and admin UI/API.
|
|
- [x] Add local startup documentation and Docker/Nginx deployment files.
|
|
- [x] Add per-generation execution and cost history to the admin API/UI.
|
|
- [ ] Run deployment smoke checks with actual provider credentials and confirm account/model availability before production.
|
|
|
|
## Out of scope / known limits
|
|
|
|
- Jobs are persisted but are executed in-process; a service restart marks active jobs failed rather than resuming them.
|
|
- SQLite WAL assumes local storage and one app process. Multi-host operation needs a different DB and durable queue.
|
|
- Upstream model availability and provider-specific parameter support vary by account and change over time. Configure and validate model IDs per provider.
|
|
- The initial admin portal has no user/session system; protect it behind network access controls and HTTPS.
|
|
- Memory use grows with uploaded image size and serialized base64/job results; the service is asynchronous, not zero-copy or constant-memory.
|