Configure the OpenAI-compatible endpoint and model #220

Closed
opened 2026-08-25 02:21:39 +01:00 by naps62-yolo · 1 comment
Owner

Decided by the operator; DESIGN.md §15 amended in ab001b5. Implement that.

The OpenAI-compatible backend's base URL and model name are database rows,
in subtitle_settings beside translation_engine, editable from /settings
without a restart. Only the API key stays in the environment.

The reasoning §15 now records: that backend is not "OpenAI", it is anything
speaking that request shape — llama.cpp, a local gateway, a hosted provider.
Which endpoint and which model are in use is something the operator tries and
changes, not a property of the deployment fixed at start-up.

What to do

  1. Migration 0028 — this number is yours; #222 takes 0029. (0026 belongs to another milestone, 0027 is taken.)
    add openai_base_url and openai_model to subtitle_settings. Both
    nullable; a null base URL means the backend's own default.
  2. Retire the bootstrap keys ARR_TRANSLATE_OPENAI_MODEL /
    translate_openai_model and ARR_TRANSLATE_OPENAI_BASE_URL /
    translate_openai_base_url from arr-daemon's config. #216 added the model
    one as an explicit stopgap; this replaces it. ARR_TRANSLATE_OPENAI_API_KEY
    stays — it is a secret, and §10 keeps those out of the database.
  3. An endpoint needing no key is valid. llama.cpp serves without
    authentication, so the backend must be constructible with a base URL and no
    API key. Do not gate construction on the key being present.
  4. Reach the running backend without a restart, the way #219 did for the
    remote-command timeout: OpenAi should re-read base URL and model per
    translation, or the settings handler should rebuild it. #219's shared-cell
    approach in crates/arr-subs/src/command.rs is the precedent — follow it
    rather than inventing a second mechanism.
  5. /settings: two fields in the SUBTITLES section (#204), shown when
    openai is among available_engines. Neither is a secret, so both render
    normally. Placeholder text should make clear a base URL is optional and
    what a local one looks like.
  6. Validate on write: a base URL that does not parse is a 422 naming the
    field, consistent with how translation_engine is already validated.

Note the health lamp (#200) probes the OpenAI-compatible engine via models;
it must probe whatever base URL is currently configured, not a start-up copy.

Depends on: #191, #198, #204, #216, #219

**Decided by the operator; DESIGN.md §15 amended in ab001b5. Implement that.** The OpenAI-compatible backend's **base URL and model name are database rows**, in `subtitle_settings` beside `translation_engine`, editable from `/settings` without a restart. Only the API key stays in the environment. The reasoning §15 now records: that backend is not "OpenAI", it is anything speaking that request shape — `llama.cpp`, a local gateway, a hosted provider. Which endpoint and which model are in use is something the operator tries and changes, not a property of the deployment fixed at start-up. ## What to do 1. **Migration `0028`** — this number is yours; #222 takes `0029`. (0026 belongs to another milestone, 0027 is taken.) add `openai_base_url` and `openai_model` to `subtitle_settings`. Both nullable; a null base URL means the backend's own default. 2. **Retire the bootstrap keys** `ARR_TRANSLATE_OPENAI_MODEL` / `translate_openai_model` and `ARR_TRANSLATE_OPENAI_BASE_URL` / `translate_openai_base_url` from `arr-daemon`'s config. #216 added the model one as an explicit stopgap; this replaces it. `ARR_TRANSLATE_OPENAI_API_KEY` stays — it is a secret, and §10 keeps those out of the database. 3. **An endpoint needing no key is valid.** `llama.cpp` serves without authentication, so the backend must be constructible with a base URL and no API key. Do not gate construction on the key being present. 4. **Reach the running backend without a restart**, the way #219 did for the remote-command timeout: `OpenAi` should re-read base URL and model per translation, or the settings handler should rebuild it. #219's shared-cell approach in `crates/arr-subs/src/command.rs` is the precedent — follow it rather than inventing a second mechanism. 5. **`/settings`**: two fields in the SUBTITLES section (#204), shown when `openai` is among `available_engines`. Neither is a secret, so both render normally. Placeholder text should make clear a base URL is optional and what a local one looks like. 6. **Validate on write**: a base URL that does not parse is a 422 naming the field, consistent with how `translation_engine` is already validated. Note the health lamp (#200) probes the OpenAI-compatible engine via `models`; it must probe whatever base URL is currently configured, not a start-up copy. Depends on: #191, #198, #204, #216, #219
naps62-yolo added this to the Subtitles milestone 2026-08-25 02:21:39 +01:00
naps62-yolo added the area/apidifficulty/easyphase/9-subtitlestype/chore labels 2026-08-25 02:21:39 +01:00
naps62-yolo changed title from Decide where the OpenAI model name is configured to Configure the OpenAI-compatible endpoint and model 2026-08-25 08:18:05 +01:00
Author
Owner

Implemented on subtitles/220-openai-endpoint.

openai_base_url and openai_model are now subtitle_settings columns
(migration 0028), both nullable, NULL meaning the backend's own default.
The backend reads them from an OpenAiEndpoint cell it re-checks on every
translate and every probe, so an edit reaches the running process without a
restart — the same shape #219 built for the remote-command timeout. The
daemon seeds that cell from the row at startup and the settings handler
repoints it on every PUT, which also moves the health lamp: verified live
against a stub models endpoint, the lamp went from unreachable to ok
on the PUT alone.

ARR_TRANSLATE_OPENAI_MODEL / translate_openai_model and
ARR_TRANSLATE_OPENAI_BASE_URL / translate_openai_base_url are gone.
deny_unknown_fields turns a config file that still carries either into a
parse error, so an operator upgrading sees the move rather than a silently
ignored setting. ARR_TRANSLATE_OPENAI_API_KEY stays, and construction
never depends on it — a llama.cpp endpoint with no key is valid.

/settings gets the two fields as their own labelled row under the
SUBTITLES section, shown when openai is among available_engines.
Placeholders say the base URL is optional and show a local one. A base URL
that does not parse is a 422 naming the field. A base URL typed without a
trailing slash is normalised, so http://box:8080/v1 reaches
/v1/chat/completions rather than the host root.

just ci passed: 714 tests, clippy clean, web typecheck and token check clean.

Findings

  • OpenAiEndpoint had to live in its own ungated module
    (arr-subs/src/openai_endpoint.rs) rather than in openai.rs, because
    pub mod openai is behind translate-openai while arr-api must validate
    and hold the endpoint in every build — the columns exist whether or not the
    backend was compiled in. Worth knowing before the next settings field that
    belongs to a feature-gated backend: the same split will be needed.
  • arr-api has no translate-* features of its own and depends on arr-subs
    without them, so any #[cfg(feature = "translate-openai")] written inside
    arr-api is always false. Nothing in the tree does that today, but it is an
    easy mistake for a later issue to make and it fails silently rather than at
    compile time.
  • The remote-command timeout field renders in the SUBTITLES grid whether or
    not command is among available_engines, unlike the new OpenAI fields.
    Not in scope here; worth an issue if the inconsistency matters.
Implemented on `subtitles/220-openai-endpoint`. `openai_base_url` and `openai_model` are now `subtitle_settings` columns (migration `0028`), both nullable, NULL meaning the backend's own default. The backend reads them from an `OpenAiEndpoint` cell it re-checks on every translate and every probe, so an edit reaches the running process without a restart — the same shape #219 built for the remote-command timeout. The daemon seeds that cell from the row at startup and the settings handler repoints it on every PUT, which also moves the health lamp: verified live against a stub `models` endpoint, the lamp went from `unreachable` to `ok` on the PUT alone. `ARR_TRANSLATE_OPENAI_MODEL` / `translate_openai_model` and `ARR_TRANSLATE_OPENAI_BASE_URL` / `translate_openai_base_url` are gone. `deny_unknown_fields` turns a config file that still carries either into a parse error, so an operator upgrading sees the move rather than a silently ignored setting. `ARR_TRANSLATE_OPENAI_API_KEY` stays, and construction never depends on it — a `llama.cpp` endpoint with no key is valid. `/settings` gets the two fields as their own labelled row under the SUBTITLES section, shown when `openai` is among `available_engines`. Placeholders say the base URL is optional and show a local one. A base URL that does not parse is a 422 naming the field. A base URL typed without a trailing slash is normalised, so `http://box:8080/v1` reaches `/v1/chat/completions` rather than the host root. `just ci` passed: 714 tests, clippy clean, web typecheck and token check clean. ## Findings - `OpenAiEndpoint` had to live in its own ungated module (`arr-subs/src/openai_endpoint.rs`) rather than in `openai.rs`, because `pub mod openai` is behind `translate-openai` while `arr-api` must validate and hold the endpoint in every build — the columns exist whether or not the backend was compiled in. Worth knowing before the next settings field that belongs to a feature-gated backend: the same split will be needed. - `arr-api` has no `translate-*` features of its own and depends on `arr-subs` without them, so any `#[cfg(feature = "translate-openai")]` written inside `arr-api` is always false. Nothing in the tree does that today, but it is an easy mistake for a later issue to make and it fails silently rather than at compile time. - The remote-command timeout field renders in the SUBTITLES grid whether or not `command` is among `available_engines`, unlike the new OpenAI fields. Not in scope here; worth an issue if the inconsistency matters.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yolo/arr#220