Skip to content

Commit d4d0bc5

Browse files
jkyberneeesclaude
andauthored
feat(docker): bundle whisper.cpp + tiny model for out-of-the-box transcription (#9)
* feat(docker): bundle whisper.cpp + tiny model for out-of-the-box transcription Ship the whisper.cpp CLI, the ggml `tiny` model, and ffmpeg in the image so the `transcribe` tool and Telegram voice auto-transcription work with zero setup — no host install, no first-run model download. Dockerfile: - New `whisper` build stage compiles whisper-cli (OpenMP off, so the only runtime shared lib is libstdc++) and fetches ggml-tiny.bin. The model is overridable via `--build-arg WHISPER_MODEL=base|small|medium`. - Runtime stage installs ffmpeg + libstdc++ and copies whisper-cli onto PATH and the model to /usr/local/share/whisper/models/. The model is baked OUTSIDE ~/.odek on purpose: the Telegram compose profiles bind-mount ./.odek over /home/odek/.odek, which would shadow a model placed under the default ~/.odek/whisper/models. A fixed image path keeps it visible across all four profiles. Config: - config.restricted.json and config.godmode.json now set a transcription block (model: tiny, auto_transcribe: true, models_dir pointing at the baked path). Docs: document bundled transcription in docker/README.md and docs/TELEGRAM.md. Verified by building the image and running OGG->WAV->whisper inference with the bundled model as the non-root odek user. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * build(docker): pin whisper.cpp to v1.8.6 for reproducible builds Clone a tagged release via a WHISPER_VERSION build arg instead of tracking master, so the bundled whisper-cli is reproducible and can't drift/break in CI on an upstream change. Verified with a no-cache build (git describe → v1.8.6, whisper-cli + ggml-tiny.bin produced). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 119b6b0 commit d4d0bc5

5 files changed

Lines changed: 66 additions & 1 deletion

File tree

docker/Dockerfile

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,49 @@ RUN go mod download
1515
COPY . .
1616
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o /out/odek ./cmd/odek
1717

18+
# ---- whisper stage ----
19+
# Build whisper.cpp's CLI and fetch the `tiny` model so the `transcribe` tool
20+
# (and Telegram voice auto-transcription) work out of the box — no host
21+
# install, no first-run model download. Same alpine base as the runtime stage
22+
# so the musl ABI matches; OpenMP is disabled to keep the runtime dependency
23+
# surface down to just libstdc++. To ship a different model, override the build
24+
# arg: `--build-arg WHISPER_MODEL=base` (tiny | base | small | medium) — size
25+
# and RAM grow accordingly. WHISPER_VERSION pins the whisper.cpp release so the
26+
# build is reproducible — bump it deliberately rather than tracking master.
27+
FROM alpine:latest AS whisper
28+
ARG WHISPER_MODEL=tiny
29+
ARG WHISPER_VERSION=v1.8.6
30+
RUN apk add --no-cache git cmake make g++ musl-dev curl
31+
RUN git clone --depth 1 --branch "${WHISPER_VERSION}" https://github.com/ggerganov/whisper.cpp /whisper
32+
WORKDIR /whisper
33+
RUN cmake -B build \
34+
-DCMAKE_BUILD_TYPE=Release \
35+
-DBUILD_SHARED_LIBS=OFF \
36+
-DGGML_OPENMP=OFF \
37+
-DWHISPER_BUILD_TESTS=OFF \
38+
-DWHISPER_BUILD_EXAMPLES=ON \
39+
&& cmake --build build -j "$(nproc)" --target whisper-cli
40+
# Fetch the ggml model into a fixed image path (NOT under ~/.odek, which the
41+
# Telegram compose profiles bind-mount over — that would hide a model baked
42+
# there). The runtime config points transcription.models_dir at this path.
43+
RUN mkdir -p /models \
44+
&& curl -fsSL -o "/models/ggml-${WHISPER_MODEL}.bin" \
45+
"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${WHISPER_MODEL}.bin"
46+
1847
# ---- runtime stage ----
1948
FROM alpine:latest
2049
# Tooling the agent commonly needs inside the sandbox container.
2150
# Trim or extend this list to taste. git + the GitHub CLI (`gh`, from the
2251
# Alpine community repo) are included so the agent can clone/PR/release.
23-
RUN apk add --no-cache ca-certificates git github-cli bash coreutils curl jq
52+
# ffmpeg converts Telegram's OGG/Opus voice notes to WAV for whisper.cpp;
53+
# libstdc++ is the only shared lib the bundled whisper-cli needs at runtime.
54+
RUN apk add --no-cache ca-certificates git github-cli bash coreutils curl jq ffmpeg libstdc++
55+
56+
# Bundle the whisper CLI + model from the whisper stage so `transcribe` works
57+
# with zero setup. whisper-cli lands on PATH; the model goes to a stable image
58+
# path that the runtime config (transcription.models_dir) points at.
59+
COPY --from=whisper /whisper/build/bin/whisper-cli /usr/local/bin/whisper-cli
60+
COPY --from=whisper /models/ /usr/local/share/whisper/models/
2461

2562
# ── Adding extra dependencies the agent can use ──────────────────────────
2663
# The agent runs shell commands INSIDE this image, so any runtime or CLI it

docker/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@ start (non-zero exit, "another schedule daemon is already running") when the bot
134134
holds it. In the reverse order (daemon up first), the bot's embedded scheduler
135135
just defers silently.
136136

137+
## Voice transcription (out of the box)
138+
139+
The image **bundles whisper.cpp's CLI and the `tiny` ggml model**, plus `ffmpeg`
140+
for OGG/Opus → WAV conversion — so the `transcribe` tool and Telegram voice
141+
auto-transcription work with zero setup. No host install, no first-run download.
142+
143+
- The model ships at `/usr/local/share/whisper/models/ggml-tiny.bin`, and both
144+
`config.restricted.json` and `config.godmode.json` point
145+
`transcription.models_dir` there. (It lives outside `~/.odek` on purpose — the
146+
Telegram profiles bind-mount `./.odek`, which would otherwise shadow it.)
147+
- Send the bot a voice note → it's transcribed locally and handed to the agent
148+
as text. `auto_transcribe` is on by default in the bundled configs.
149+
- Want a more accurate (larger) model? Rebuild with
150+
`--build-arg WHISPER_MODEL=base` (or `small` / `medium`) and bump the
151+
`model` field in the config to match.
152+
137153
## Verify the profiles differ
138154

139155
- **Restricted**: ask it to `rm -rf` everything in `/workspace` → denied, never runs.

docker/config.godmode.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
"skills": {
88
"verbose": true
99
},
10+
"transcription": {
11+
"model": "tiny",
12+
"auto_transcribe": true,
13+
"models_dir": "/usr/local/share/whisper/models"
14+
},
1015
"dangerous": {
1116
"action": "allow",
1217
"non_interactive": "allow"

docker/config.restricted.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
"skills": {
88
"verbose": true
99
},
10+
"transcription": {
11+
"model": "tiny",
12+
"auto_transcribe": true,
13+
"models_dir": "/usr/local/share/whisper/models"
14+
},
1015
"dangerous": {
1116
"non_interactive": "deny",
1217
"classes": {

docs/TELEGRAM.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ Voice message received → DownloadVoice (OGG Opus to disk)
312312

313313
**Fallback:** If auto-transcribe fails (ffmpeg unavailable, corrupt audio, whisper error), the agent receives the file path with a suggestion to use the `transcribe()` tool manually.
314314

315+
**Docker:** the official image bundles the whisper.cpp CLI, the `tiny` model, and ffmpeg, with `auto_transcribe` enabled in the shipped configs — so voice transcription works out of the box with no host install. See [../docker/README.md](../docker/README.md#voice-transcription-out-of-the-box).
316+
315317
### Tool Progress (Narrator)
316318

317319
Tool progress shows what the agent is doing in real time. Controlled by the `tool_progress` config field (independent from `interaction_mode`):

0 commit comments

Comments
 (0)