跳转到内容

Response Node

此内容尚不支持你的语言。

The response node is the only way a workflow talks back to the user directly. It renders a Handlebars template against the current execution context and sends the result as the agent’s reply for this turn.

See Workflow Overview for the node/edge envelope this fits into.

FieldTypeRequiredDescription
messageTemplatestringYesThe Handlebars template rendered into the outgoing message. Supports {{variable}} interpolation as well as block helpers — {{#if}} and {{#each}} — over context variables.
moodstringNoA free-form hint describing the emotional tone of this reply (e.g. "helpful", "concerned", "neutral"). Passed through to the message record; if omitted, defaults to "neutral".
structuredContentTemplatestringNoAn optional second Handlebars template, rendered independently of messageTemplate, whose output must be JSON matching a closed vocabulary — see Structured content below.

Structured content (structuredContentTemplate)

Section titled “Structured content (structuredContentTemplate)”

structuredContentTemplate lets a response node attach an optional structured payload alongside its plain-text reply — a card, a table, or a list — without replacing the text. A client that doesn’t render it (which is every client except the Wetel dashboard today — see below) just shows text as normal.

  • Rendered via interpolateJson, not plain interpolation. Because the output must be valid JSON, use {{jsonString someVar}} for any interpolated value that could contain a quote, a newline, or other characters that would otherwise corrupt the JSON — the same convention as webhook’s bodyTemplate and tool/action’s argsTemplate. See Best Practices for the full jsonString rationale.
  • Closed vocabulary — exactly 3 shapes, nothing else:
    type StructuredContent =
    | {
    kind: "card";
    title: string;
    subtitle?: string;
    fields: { label: string; value: string }[];
    imageUrl?: string;
    }
    | { kind: "table"; columns: string[]; rows: string[][] }
    | { kind: "list"; items: string[]; ordered?: boolean };
  • Fails safe, always. If the template is omitted, fails to compile, renders to something that isn’t valid JSON, or renders valid JSON that doesn’t match one of the 3 shapes above, the node does not fail — structuredContent is simply null for that message, and the plain-text messageTemplate reply still sends as normal. Never rely on structuredContent being present; always design for a client that only reads text.
  • Dashboard-only consumer today. The Wetel dashboard’s own agent-testing chat surface renders card/table/list alongside the plain-text reply. The <vai-avatar> embed SDK and any REST/SDK integration reading MessageDto directly can read the field (it’s a normal JSON GraphQL scalar, see Sessions API Reference) but nothing renders it there yet.
  • No interactive/button variant yet. This is read-only structured display content — there’s no way (yet) for a rendered widget to submit a value back into the conversation.
{
"id": "respond_order_status",
"type": "response",
"label": "Respond with Order Status",
"config": {
"messageTemplate": "Here's your order status.",
"structuredContentTemplate": "{\"kind\":\"card\",\"title\":\"Order #{{orderResult.id}}\",\"subtitle\":\"{{orderResult.status}}\",\"fields\":[{\"label\":\"Placed\",\"value\":\"{{jsonString orderResult.placedAt}}\"},{\"label\":\"ETA\",\"value\":\"{{jsonString orderResult.eta}}\"}]}"
},
"position": { "x": 300, "y": 200 }
}
  • messageTemplate is rendered with plain-text (no-escape) interpolation — the same rendering convention used by WebhookNodeConfig.bodyTemplate and ToolNodeConfig.argsTemplate, as opposed to HTML-escaping. Values are inserted as-is. If you’re hand-authoring a JSON envelope as your outgoing text (e.g. {"message": "...", "type": "..."}), any variable going inside a JSON string literal needs the jsonString helper, not a bare {{variable}} — see Best Practices: building a hand-authored JSON envelope inside a text field.
  • Block helpers work as expected: {{#if orderFound}}Your order is on the way.{{else}}I couldn't find that order.{{/if}} and {{#each items}}{{this}}, {{/each}} are both valid, since these are standard Handlebars built-ins.
  • Sending the message and recording it are not optional side effects you configure — every response node execution publishes the rendered text to the live session subscription and persists it as a message. If the template fails to render (a template syntax error, or referencing a variable in a way interpolateText can’t resolve), the node throws, and the workflow run is recorded as failed rather than silently sending nothing.
  • A response node’s own outgoing edge (if it has one) is followed unconditionally — response is not a branching node type. It typically connects straight to another response, a further processing step, or an end_session node.

A generic order-status assistant renders a different message depending on whether an earlier tool node found a matching order, using {{#if}}/{{else}}:

{
"id": "respond_order_status",
"type": "response",
"label": "Respond with Order Status",
"config": {
"messageTemplate": "{{#if orderFound}}Your order #{{orderId}} is currently {{orderStatus}}.{{else}}I couldn't find an order matching that — could you double check the order number?{{/if}}",
"mood": "helpful"
},
"position": { "x": 300, "y": 200 }
}

Here, orderFound and orderStatus would have been written into the execution context by an earlier tool node’s outputVar (or by a follow-up llm node that shaped the tool’s raw JSON result into flatter fields). A minimal linear workflow tying this together:

{
"nodes": [
{
"id": "start",
"type": "start",
"label": "Start",
"config": {},
"position": { "x": 0, "y": 0 }
},
{
"id": "lookup_order",
"type": "tool",
"label": "Look Up Order",
"config": {
"connectorId": 7,
"toolName": "get_order_status",
"outputVar": "orderResult",
"argsTemplate": "{\"orderQuery\": \"{{userMessage}}\"}"
},
"position": { "x": 0, "y": 100 }
},
{
"id": "respond_order_status",
"type": "response",
"label": "Respond with Order Status",
"config": {
"messageTemplate": "{{#if orderResult.found}}Your order #{{orderResult.id}} is currently {{orderResult.status}}.{{else}}I couldn't find an order matching that — could you double check the order number?{{/if}}",
"mood": "helpful"
},
"position": { "x": 0, "y": 200 }
},
{
"id": "end",
"type": "end_session",
"label": "End",
"config": {},
"position": { "x": 0, "y": 300 }
}
],
"edges": [
{ "id": "e1", "source": "start", "target": "lookup_order" },
{ "id": "e2", "source": "lookup_order", "target": "respond_order_status" },
{ "id": "e3", "source": "respond_order_status", "target": "end" }
]
}

Note this example accesses orderResult.found / orderResult.id / orderResult.status — nested property access is fine here because messageTemplate is rendered by a real Handlebars engine, unlike a condition node’s expression, which uses a much more restrictive grammar with bare variable names only. Don’t confuse the two templating/evaluation contexts when moving between node types.