Skip to content

Generate Files: Voice and Video

This recipe builds a single workflow that produces four artifacts for one customer briefing: a markdown summary document, a spoken voice-over, a short jingle and a narrated video script. It then shows how your integration reads each artifact and presents or downloads it.

Every artifact type used here is behind a platform switch that is off by default. Your integration cannot use these nodes until Wetel has switched them on for your environment.

SwitchControlsWho turns it on
Artifact generationmarkdown, html_page, video_scriptWetel staff, per environment
Image generationimage (not used in this recipe)Wetel staff, per environment
Audio generationaudio_speech, audio_musicWetel staff, per environment
Video narrationnarrate: true on video_scriptWetel staff, per environment

While a switch is off, the node fails immediately with the code DISABLED and no model call is made. Do not ship a customer-facing flow until you have confirmed with Wetel that every switch you need is on.

Audio artifacts have per-workspace daily limits that apply regardless of how many workflows or sessions produce them:

Artifact typeDefault daily limitMax file size
audio_speech10 files per workspace per day20 MB
audio_music5 files per workspace per day20 MB

When narrate is true on a video_script, each scene counts as one speech file against the daily speech limit. A scene whose speech file cannot be produced (for example because the limit was reached mid-video) simply has no voice; the video still succeeds.

All artifact types also count toward the workspace’s overall daily artifact limit and monthly spend cap. See the Generate Artifact Node reference for details.

The workflow has five nodes in sequence: a set_variable node to prepare the input, then four generate_artifact nodes. Each artifact node writes an artifactId to a different output variable. A final llm node composes the reply to the user.

Every generate_artifact node has a failure edge so the conversation keeps going even when one artifact cannot be produced.

flowchart TD
start([start]) --> prep["prepare_inputs (set_variable)"]
prep --> doc["gen_document (markdown)"]
doc -- success --> voice["gen_voiceover (audio_speech)"]
doc -- failure --> docfail["handle_doc_failure (llm)"] --> voice
voice -- success --> jingle["gen_jingle (audio_music)"]
voice -- failure --> voicefail["handle_voice_failure (llm)"] --> jingle
jingle -- success --> video["gen_video (video_script, narrate)"]
jingle -- failure --> musicfail["handle_music_failure (llm)"] --> video
video -- success --> reply["compose_reply (llm)"]
video -- failure --> videofail["handle_video_failure (llm)"] --> reply
reply --> done([end])

See Workflow Overview for the node/edge envelope and Fail Gracefully for the failure-routing pattern.

{
"id": "prepare_inputs",
"type": "set_variable",
"config": {
"assignments": [
{
"variable": "customerName",
"expression": "channel.raw.customerName || 'Valued Customer'"
},
{ "variable": "briefing", "expression": "lastUserMessage" }
]
}
}
{
"id": "gen_document",
"type": "generate_artifact",
"config": {
"artifactType": "markdown",
"titleTemplate": "Briefing for {{customerName}}",
"promptTemplate": "Write a one-page executive summary of this briefing for the customer. Briefing: {{briefing}}",
"outputVar": "docResult",
"timeoutMs": 90000
}
}

On success, docResult contains { "artifactId": 501 } (the actual id will differ).

{
"id": "gen_voiceover",
"type": "generate_artifact",
"config": {
"artifactType": "audio_speech",
"titleTemplate": "Voice-over for {{customerName}}",
"promptTemplate": "Read this briefing summary aloud in a clear, professional tone: {{briefing}}",
"voice": "Aoede",
"outputVar": "voiceResult",
"timeoutMs": 120000
}
}

The voice field accepts one of Kore, Charon, Fenrir, Aoede or Puck. If omitted it defaults to Kore. The resulting artifact is a WAV file.

{
"id": "gen_jingle",
"type": "generate_artifact",
"config": {
"artifactType": "audio_music",
"titleTemplate": "Jingle for {{customerName}}",
"promptTemplate": "Compose a short, upbeat corporate jingle, about 15 seconds, suitable for an introduction to a briefing video.",
"outputVar": "jingleResult",
"timeoutMs": 120000
}
}

The resulting artifact is an MP3 file.

5. Generate the narrated video script (video_script)

Section titled “5. Generate the narrated video script (video_script)”
{
"id": "gen_video",
"type": "generate_artifact",
"config": {
"artifactType": "video_script",
"titleTemplate": "Video briefing for {{customerName}}",
"promptTemplate": "Write a 45-second narrated explainer video about this briefing. Open with a hook, present 2-3 key points on cards, and close with a call to action. Briefing: {{briefing}}",
"narrate": true,
"outputVar": "videoResult",
"timeoutMs": 180000
}
}

What you get. A video_script artifact is a small JSON manifest of 1 to 8 scenes (hook, cards, table, flow and a closing call to action), each with a spoken narration. It is not a server-rendered MP4 file. The workspace app plays it in the Documents window, and the “Download video” button records an MP4 (or WebM) in the viewer’s own browser in real time. The spoken voice is included only when narrate is true and the video narration switch is on; each scene’s speech file is a separate speech artifact that counts against the daily speech limit. Scenes carry no URLs or markup.

If your integration needs to present the video outside the workspace app, you will need to either play the JSON manifest in your own player or have the end user record the MP4 from the browser.

Each generate_artifact node should have a failure edge. On the failure path, use a lightweight llm node or set_variable node to set a message telling the user that one part could not be produced.

The failure handler has access to _lastNodeErrorCode and _lastNodeErrorPublic. Show only _lastNodeErrorPublic to the end user; _lastNodeError can contain host names and provider details.

You can branch on the code with a switch node. The codes the generate_artifact node can produce are:

CodeMeaningTypical handling
DISABLEDThe platform switch is off.Tell the user the feature is not available. Do not retry.
QUOTA_EXCEEDEDDaily artifact limit or concurrency limit reached.Tell the user to try again tomorrow. Do not retry.
SPEND_CAPMonthly spend cap reached.Tell the user the workspace limit has been reached.
INVALID_INPUTBad type, empty/oversized/credential-shaped prompt.A workflow authoring bug. Fix the template.
BLOCKEDThe model declined the prompt, or a video script was invalid.Tell the user the content could not be generated.
TOO_LARGEThe produced file exceeds the size limit.Simplify the prompt.
PROVIDER_UNAVAILABLENo provider configured or provider failed.Retry may help. Consider a retry policy on an upstream node; see Build a Reliable Workflow.
INTERNALStorage failure.Retry may help.

Example failure-handler node:

{
"id": "handle_voice_failure",
"type": "set_variable",
"config": {
"assignments": [
{ "variable": "voiceResult", "expression": "{ \"artifactId\": null }" },
{ "variable": "voiceError", "expression": "_lastNodeErrorPublic" }
]
}
}

See Fail Gracefully for the full pattern.

After the workflow finishes, each output variable holds { "artifactId": <number> } (or null if that step failed). Your integration reads the artifact and gets a download link through the Artifacts API.

query GetArtifact($id: Int!) {
artifact(id: $id) {
id
kind
status
title
mimeType
sizeBytes
errorCode
}
}
{ "id": 501 }
{
"data": {
"artifact": {
"id": 501,
"kind": "markdown",
"status": "READY",
"title": "Briefing for Acme Corp",
"mimeType": "text/markdown",
"sizeBytes": 3210,
"errorCode": null
}
}
}

An artifact may briefly be PENDING while it is being stored. Poll until status is READY or FAILED. If FAILED, errorCode explains why.

mutation GetDownloadUrl($id: Int!) {
artifactDownloadUrl(id: $id) {
artifactId
url
expiresAt
}
}
{ "id": 501 }

The returned url is short-lived: it stops working at expiresAt, so ask for a fresh one each time instead of storing it. For audio artifacts (WAV or MP3), your client can play the URL directly. For the document, render or display the markdown in your UI.

If your integration surfaces all files a conversation produced, use the artifacts query:

query SessionArtifacts($sessionId: Int!) {
artifacts(sessionId: $sessionId, first: 20) {
edges {
node {
id
kind
status
title
mimeType
sizeBytes
}
}
pageInfo {
hasNextPage
endCursor
}
}
}

An end user in your chat UI can use the avatarToken from sdkStart to read and download artifacts from their own session. A dashboard JWT can access any artifact in the workspace. An API key is not accepted for artifact operations. See the Artifacts API for details.

Artifact kindFile formatHow to present
markdownText (Markdown)Render in your UI or offer as a .md download.
audio_speechWAVPlay with an <audio> element or offer as a download.
audio_musicMP3Play with an <audio> element or offer as a download.
video_scriptJSON manifestThe workspace app’s Documents window plays it and offers “Download video,” which records an MP4 or WebM in the browser. If you are building your own player, you will need to interpret the scene manifest. This is not an MP4 served from the platform.

A draft test (testDraft: true on sendMessage) runs in mock mode by default. Each generate_artifact node checks its config and templates exactly as in a live run, then produces { "artifactId": null, "mocked": true } without calling any model, storing any file or consuming any quota. This lets you verify the entire graph, including failure edges, without cost.

See Workflow Testing for mock mode details.

For reference, here is the full set of nodes in this recipe’s workflow:

[
{
"id": "prepare_inputs",
"type": "set_variable",
"config": {
"assignments": [
{
"variable": "customerName",
"expression": "channel.raw.customerName || 'Valued Customer'"
},
{ "variable": "briefing", "expression": "lastUserMessage" }
]
}
},
{
"id": "gen_document",
"type": "generate_artifact",
"config": {
"artifactType": "markdown",
"titleTemplate": "Briefing for {{customerName}}",
"promptTemplate": "Write a one-page executive summary of this briefing for the customer. Briefing: {{briefing}}",
"outputVar": "docResult",
"timeoutMs": 90000
}
},
{
"id": "gen_voiceover",
"type": "generate_artifact",
"config": {
"artifactType": "audio_speech",
"titleTemplate": "Voice-over for {{customerName}}",
"promptTemplate": "Read this briefing summary aloud in a clear, professional tone: {{briefing}}",
"voice": "Aoede",
"outputVar": "voiceResult",
"timeoutMs": 120000
}
},
{
"id": "gen_jingle",
"type": "generate_artifact",
"config": {
"artifactType": "audio_music",
"titleTemplate": "Jingle for {{customerName}}",
"promptTemplate": "Compose a short, upbeat corporate jingle, about 15 seconds, suitable for an introduction to a briefing video.",
"outputVar": "jingleResult",
"timeoutMs": 120000
}
},
{
"id": "gen_video",
"type": "generate_artifact",
"config": {
"artifactType": "video_script",
"titleTemplate": "Video briefing for {{customerName}}",
"promptTemplate": "Write a 45-second narrated explainer video about this briefing. Open with a hook, present 2-3 key points on cards, and close with a call to action. Briefing: {{briefing}}",
"narrate": true,
"outputVar": "videoResult",
"timeoutMs": 180000
}
},
{
"id": "handle_doc_failure",
"type": "set_variable",
"config": {
"assignments": [
{ "variable": "docResult", "expression": "{ \"artifactId\": null }" },
{ "variable": "docError", "expression": "_lastNodeErrorPublic" }
]
}
},
{
"id": "handle_voice_failure",
"type": "set_variable",
"config": {
"assignments": [
{ "variable": "voiceResult", "expression": "{ \"artifactId\": null }" },
{ "variable": "voiceError", "expression": "_lastNodeErrorPublic" }
]
}
},
{
"id": "handle_music_failure",
"type": "set_variable",
"config": {
"assignments": [
{
"variable": "jingleResult",
"expression": "{ \"artifactId\": null }"
},
{ "variable": "musicError", "expression": "_lastNodeErrorPublic" }
]
}
},
{
"id": "handle_video_failure",
"type": "set_variable",
"config": {
"assignments": [
{ "variable": "videoResult", "expression": "{ \"artifactId\": null }" },
{ "variable": "videoError", "expression": "_lastNodeErrorPublic" }
]
}
},
{
"id": "compose_reply",
"type": "llm",
"config": {
"promptTemplate": "Tell the customer their briefing materials are ready. Mention which items were produced and which could not be generated. Document: {{docResult.artifactId}}, Voice-over: {{voiceResult.artifactId}}, Jingle: {{jingleResult.artifactId}}, Video: {{videoResult.artifactId}}."
}
}
]

The edges between nodes (including success and failure edges) follow the graph described above. See Workflow Overview for the edge format.