Summary
Introduce `openpalm/services` — a single custom FastAPI container (one port, four OpenAI-compatible endpoint groups) for local AI on a single-user host. Consolidates #431 (image gen). Removes `openpalm/voice` — the voice container code is the starting point for `containers/services/`, not a parallel path.
Full design: `docs/technical/local-ai-unified-container-plan.md`
Milestone scope (0.13.0 — all phases)
GPU variants are the goal. The three phases are sequential implementation steps within this milestone.
| Phase |
Scope |
| 1 — CPU voice + embeddings |
`containers/services/` from voice foundation; onnxruntime embeddings; `addon.services.cpu` profile; akm wiring |
| 2 — Chat + image gen (CPU baseline) |
llama-cpp-python (GGUF), diffusers, Connection wiring, `OP_IMAGEGEN_BASE_URL` |
| 3 — GPU variants (all three) |
CUDA, ROCm, Intel — same Dockerfile targets, matrix CI, compose overlays |
Capabilities
| Endpoint |
Library |
| `POST /v1/audio/speech` |
Kokoro (from `containers/voice/`) |
| `POST /v1/audio/transcriptions` |
faster-whisper |
| `POST /v1/embeddings` |
onnxruntime + nomic-embed-text-v1.5 ONNX (768 dims) |
| `POST /v1/chat/completions` |
llama-cpp-python (GGUF) |
| `POST /v1/images/generations` |
diffusers + torch |
| `GET /health` |
per-capability status |
What is removed
- `containers/voice/` — deleted after `containers/services/` is complete
- `openpalm/voice` addon name, `addon.voice.*` compose profiles
- `OP_VOICE_PROFILE`, `OP_VOICE_IMAGE_TAG`, `OP_VOICE_PORT_HOST`, `OP_VOICE_BIND_ADDRESS`, `OP_VOICE_WHISPER_MODEL`, `OP_VOICE_KOKORO_VOICE` env vars
- `publish-voice.yml` + `publish-voice-models.yml` workflows
- `voice` / `voice-cuda` / `voice-rocm` service blocks from `services.compose.yml`
- Voice-specific docs (`docs/technical/openpalm-voice-addon.md`, `docs/technical/voice-container-build.md`)
Compose service naming
Compose service name and network alias are both `services`. All in-compose URLs use `http://services:4114/v1\`. The CPU variant uses the service name as its implicit alias; GPU variants add explicit `aliases: [services]` — same pattern as ollama.
Non-goals / explicitly rejected
- No `OP_SERVICES_ENABLE_*` flags. Missing model file → 503.
- No in-container supervisor. One uvicorn process.
- No gateway/routing layer. One FastAPI app; route handlers call model instances directly.
- No Ollama replacement. Ollama remains the advanced LLM addon.
Engineering changes required
1. `resolveActiveProfiles()` in `compose-args.ts` — replace `voice`/`ollama` if/else with data-driven map, add `services`:
```ts
const HARDWARE_PROFILED_ADDONS = {
ollama: { env: 'OP_OLLAMA_PROFILE', default: 'addon.ollama.cpu' },
services: { env: 'OP_SERVICES_PROFILE', default: 'addon.services.cpu' },
};
```
2. `addons.ts` — replace `'voice'` in `BUILTIN_ADDONS`, update `listEnabledAddonIds()` profile env scan, rename `voiceImageRef()` → `servicesImageRef()`.
3. `addon-env-schemas.ts` — replace `voice` schema entry with `services`.
4. `voice-env.ts` — update `openpalmVoiceBaseURL()` to read `OP_SERVICES_PORT_HOST` / port 4114; rename engine preset `openpalm-voice` → `openpalm-services`.
5. `setup.ts` — rename `voiceProfile` → `servicesProfile`; use addon name `services`.
6. UI routes — `/admin/voice` → `/admin/services`, `/api/setup/voice-profiles` → `/api/setup/services-profiles`.
7. Migration — rename `voice` → `services` in `OP_ENABLED_ADDONS`; rewrite `OP_VOICE_PROFILE` → `OP_SERVICES_PROFILE` (translating variant); rewrite `OP_TTS/STT_BASE_URL` port 8880 → 4114; rename `data/voice/` → `data/services/` (mv, not copy).
Capability wiring
Acceptance criteria (e2e proof required)
- `curl :4114/v1/audio/speech` returns audio.
- `curl :4114/v1/audio/transcriptions` returns transcript.
- `curl :4114/v1/embeddings` returns `data[].embedding` of length 768.
- AKM in the assistant container performs a real memory write via `http://services:4114/v1\`.
- `curl :4114/v1/chat/completions` returns a completion from the local GGUF model.
- `curl :4114/v1/images/generations` returns a base64 image.
- `resolveActiveProfiles` with `services` enabled emits `addon.services.cpu`.
- CUDA variant: GPU acceleration verified via `nvidia-smi` inside the container.
- Existing installs: migration correctly renames addon, profiles, env vars, and data dir.
- `openpalm/voice` addon is absent from the stack — no voice container starts.
Closes #431.
Summary
Introduce `openpalm/services` — a single custom FastAPI container (one port, four OpenAI-compatible endpoint groups) for local AI on a single-user host. Consolidates #431 (image gen). Removes `openpalm/voice` — the voice container code is the starting point for `containers/services/`, not a parallel path.
Full design: `docs/technical/local-ai-unified-container-plan.md`
Milestone scope (0.13.0 — all phases)
GPU variants are the goal. The three phases are sequential implementation steps within this milestone.
Capabilities
What is removed
Compose service naming
Compose service name and network alias are both `services`. All in-compose URLs use `http://services:4114/v1\`. The CPU variant uses the service name as its implicit alias; GPU variants add explicit `aliases: [services]` — same pattern as ollama.
Non-goals / explicitly rejected
Engineering changes required
1. `resolveActiveProfiles()` in `compose-args.ts` — replace `voice`/`ollama` if/else with data-driven map, add `services`:
```ts
const HARDWARE_PROFILED_ADDONS = {
ollama: { env: 'OP_OLLAMA_PROFILE', default: 'addon.ollama.cpu' },
services: { env: 'OP_SERVICES_PROFILE', default: 'addon.services.cpu' },
};
```
2. `addons.ts` — replace `'voice'` in `BUILTIN_ADDONS`, update `listEnabledAddonIds()` profile env scan, rename `voiceImageRef()` → `servicesImageRef()`.
3. `addon-env-schemas.ts` — replace `voice` schema entry with `services`.
4. `voice-env.ts` — update `openpalmVoiceBaseURL()` to read `OP_SERVICES_PORT_HOST` / port 4114; rename engine preset `openpalm-voice` → `openpalm-services`.
5. `setup.ts` — rename `voiceProfile` → `servicesProfile`; use addon name `services`.
6. UI routes — `/admin/voice` → `/admin/services`, `/api/setup/voice-profiles` → `/api/setup/services-profiles`.
7. Migration — rename `voice` → `services` in `OP_ENABLED_ADDONS`; rewrite `OP_VOICE_PROFILE` → `OP_SERVICES_PROFILE` (translating variant); rewrite `OP_TTS/STT_BASE_URL` port 8880 → 4114; rename `data/voice/` → `data/services/` (mv, not copy).
Capability wiring
Acceptance criteria (e2e proof required)
Closes #431.