Skip to content

feat(workflows): make the voice transcription template real - #2498

Open
Hoang130203 wants to merge 1 commit into
Osmantic:mainfrom
Hoang130203:feat/n8n-voice-transcription-workflow
Open

feat(workflows): make the voice transcription template real#2498
Hoang130203 wants to merge 1 commit into
Osmantic:mainfrom
Hoang130203:feat/n8n-voice-transcription-workflow

Conversation

@Hoang130203

Copy link
Copy Markdown

Summary

config/n8n/03-voice-transcription.json was a placeholder — a manualTrigger,
a sticky note reading "Customize the nodes below to match your setup", and
"connections": {} — behind a catalog card advertising "Transcribe audio
files to text"
with "setupTime": "1 minute".

Webhook POST /webhook/ods-transcribe   (multipart)
  -> Audio Attached?  (IF)
       true  -> whisper /v1/audio/transcriptions -> Return Transcript (200 JSON)
       false -> Return 400
curl -X POST http://localhost:5678/webhook/ods-transcribe \
  -F 'file=@meeting.wav'

Optional form fields: model (default Systran/faster-whisper-base, the
model validate-models.py treats as the STT default).

  • The upload is streamed to Whisper as multipart, not base64'd through
    JSON, so a long recording does not get inflated by a third in memory.
  • The request goes to http://whisper:8000 — the manifest's in-network port,
    not the published 9000 — so the audio stays on ods-network.
  • A request with no file answers 400 with the curl form to use, instead of
    failing inside the HTTP node with an opaque multipart error.

The subtle part, and why it is worth a look

n8n's webhook does not name a multipart upload data. handleFormData()
in Webhook/utils.js names the binary property after the form field key:

for (const key of Object.keys(files)) {
    let binaryPropertyName = key;
    ...
    if (options.binaryPropertyName) {
        binaryPropertyName = `${options.binaryPropertyName}${count}`;
    }

So setting options.binaryPropertyName: "data" does not rename it to data
it appends a counter and yields data0. My first draft did exactly that
and referenced data, which would have imported cleanly, passed every static
check, and then silently taken the 400 branch on every upload.

The workflow now leaves that option unset and refers to file throughout,
matching the field name in the documented curl.

AI Assistance

AI assisted with drafting the node graph and this description. I verified the
Whisper endpoint and port against extensions/services/whisper/, and found the
binary-naming defect by reading n8n's own handleFormData implementation
rather than assuming.

Release Lane

  • Stable hotfix targeting release/2.6.x
  • Mainline change targeting main
  • Next-minor work targeting the next feature/minor release
  • Not sure; reviewer should help classify

Stable hotfix reason:

n/a

Changed Surface

  • Docs only
  • Tests only
  • Dashboard UI
  • Dashboard API / host agent
  • Installer / bootstrap / lifecycle
  • Docker Compose / service manifests
  • Model routing / Hermes / capabilities
  • Network exposure / auth / proxy
  • Dependencies / runtime wiring

(One JSON file under config/n8n/. An import payload for n8n; no ODS code
executes it. The catalog entry is unchanged.)

Risk And Validation

  • Risk level: Low
  • Validation run:
    • git diff --check
    • Markdown/link sanity for docs
    • Focused tests listed below
    • Dashboard lint/test/build
    • Extension audit / compose validation
    • Release-grade fleet or scoped hardware validation
    • Stable-lane patch validation, if targeting release/2.6.x

Commands/results:

# Validated against the exact node package ODS ships. compose.yaml pins
# n8nio/n8n:2.6.4; `npm view n8n@2.6.4 dependencies.n8n-nodes-base` -> 2.6.2.

$ node verify.js 03-voice-transcription.json
n8n-nodes-base version: 2.6.2
node types loaded: 417
  checked 03-voice-transcription.json: 6 nodes

ALL WORKFLOWS VALID

# The harness loads every node class out of n8n-nodes-base and asserts per node:
#   type resolves, typeVersion is declared, every parameter key is a real
#   property, node names unique, every connection endpoint exists.
#
# For this workflow I additionally checked the multipart shapes against the
# node definitions rather than from memory:
#   httpRequest contentType options -> form-urlencoded, multipart-form-data,
#                                      json, binaryData, raw
#   multipart bodyParameters.parameters values -> parameterType, name, value,
#                                                 inputDataFieldName
#   webhook options -> binaryData, binaryPropertyName, ignoreBots, ipWhitelist,
#                      noResponseBody, responsePropertyName, rawBody, ...

Caveat: Docker is not running on my dev host, so I could not stand up
n8n + whisper and post a real recording. The validation above is static against
the real node definitions plus a read of n8n's multipart handler. Return Transcript reads $json.text, which is the OpenAI-compatible transcription
shape speaches returns and the field scripts/ods-test-functional.sh asserts
on. Happy to get a live run on a machine with Docker before you merge.

Operational Change Check

An import payload for n8n. Nothing in the installer, compose stack, ods-cli,
or dashboard-api executes it — dashboard-api reads only catalog.json for the
Workflows listing. No existing install changes until a user imports it.

  • This is not an operational change.
  • This is an operational change and validation is recorded above.
  • This is an operational change and validation is intentionally deferred for:

Notes For Reviewers

Model default. The form defaults to Systran/faster-whisper-base, matching
scripts/validate-models.py's STT default. On an NVIDIA install the tier map
pins deepdml/faster-whisper-large-v3-turbo-ct2 instead, and this workflow
will not know that — the caller can pass -F 'model=...'. Wiring the
configured value in would need n8n to have AUDIO_STT_MODEL in its
environment, which compose.yaml does not pass today. Tell me if you want that
plumbing and I will send it separately.

Webhook path. ods-transcribe. No other catalog workflow claims it.

Part of the series making the 18 stub templates real: #2496 (chat,
code-assistant), #2497 (summarizer). Independent files, no overlapping lines —
any order is fine.

config/n8n/03-voice-transcription.json was a manualTrigger plus a sticky
note saying "Customize the nodes below to match your setup", with
"connections": {} — an empty canvas behind a catalog card advertising
"Transcribe audio files to text".

Now: POST /webhook/ods-transcribe with a multipart audio file ->
whisper /v1/audio/transcriptions -> JSON {text, duration, language}.

    curl -X POST http://localhost:5678/webhook/ods-transcribe \
      -F 'file=@meeting.wav'

The file is streamed to Whisper as multipart rather than base64'd through
JSON, and the request goes to whisper:8000 on ods-network, so the audio
never leaves the box.

An upload with no file answers 400 with the curl form to use, instead of
failing inside the HTTP node with a multipart error.

Note on the binary property name: n8n's webhook names a multipart upload
after the form field key, so the file arrives as `file`, not `data`.
Setting options.binaryPropertyName does not rename it to `data` — the
node appends a counter and produces `data0`. The workflow therefore
leaves that option unset and refers to `file` throughout, matching the
curl in the sticky note.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant