LLM Node
此内容尚不支持你的语言。
The llm node is the only node type that calls a language model. Every other node reacts to text, routes on a value, or does I/O — llm is where a workflow actually thinks: answering a question, classifying an intent, extracting a value from free text, or drafting a reply that a downstream response node will send.
See Workflow Overview for the node/edge envelope this fits into.
Config fields
Section titled “Config fields”| Field | Type | Required | Description |
|---|---|---|---|
promptTemplate | string | Yes | The Handlebars-templated prompt sent to the model. Can reference any variable in the execution context, e.g. {{userMessage}} or an outputVar written by an earlier node. |
outputVar | string | Yes | The context variable the model’s response is written to. Must not collide with a reserved context key (sessionId, tenantId, userMessage, conversationHistory, turnCount, agentId, clientExternalId, interruptGeneration) or any name starting with _ — the workflow fails validation at save time if it does. |
knowledgeBaseId | number | No | Attaches a knowledge base for retrieval-augmented generation. When set, relevant chunks are retrieved for the current userMessage and prepended to the effective system prompt before the model call. If retrieval itself fails (a transient vector-store error), the node fails open — it proceeds without grounding rather than failing the whole node. |
systemPrompt | string | No | A static, not Handlebars-rendered system prompt. When set, it’s used verbatim — the rendered promptTemplate is used for token-cost accounting only and is not sent to the model in any form. Leave this unset in almost every case; see the callout below. |
modelOverride | string | No | Use a specific model for this node instead of the tenant/agent default. |
streamOutput | boolean | No | Controls whether this node’s output streams to the client as it’s generated. Defaults to true (streaming) — you must explicitly set false to disable it. See the callout below. |
applyVoiceFormatting | boolean | No | Appends spoken-medium formatting rules (no markdown, no bullet points, numbers spelled out, 1–3 sentences) to the system prompt. Intended only for nodes whose output flows to a voice-facing response node. See the callout below — never set this on a node feeding a condition or router. |
extractFirstInteger | boolean | No | When true, the executor discards the model’s raw text and instead regex-extracts the first integer found anywhere in the response, storing that as a number (since 2026-09-11 — previously a string) in outputVar — falling back to 0 if no digit is found. See the callout below. |
excludeHistory | boolean | No | When true, this node’s underlying model call receives an empty conversation history instead of the session’s real conversationHistory. See the callout below. |
allowGeneralKnowledgeFallback | boolean | No | Only relevant when knowledgeBaseId is also set. Softens the retrieval-augmentation directive from “answer using ONLY this reference material” to “use it if relevant, otherwise answer from general knowledge and say so.” See the callout below. |
matchAnyOf | string[] | No | For a classification node feeding a router or condition. When set, the executor scans the model’s raw response for the earliest-position candidate label from this list and stores that in outputVar instead of the raw text — falling back to the raw text unchanged if none of the candidates appear anywhere in it. See the callout below. |
imageVars | string[] | No | Added 2026-09-17. A list of context variable names whose values are attached to this call as images (vision input), on top of promptTemplate’s text. See the callout below — read it before using this on a node whose provider might not support images. |
Gotcha: setting systemPrompt silently drops everything in promptTemplate
Section titled “Gotcha: setting systemPrompt silently drops everything in promptTemplate”A workflow that queries a tool/action for data and asks an llm node to answer using it is the case most likely to hit this: set systemPrompt to hold grounding instructions and promptTemplate to hold {{jsonString someActionOutput}} plus the question, and the model receives the instructions with zero actual data — it doesn’t error, it just answers from whatever it can infer from the conversation, which for a data-lookup question means a confident, plausible-sounding, entirely fabricated answer. Nothing in the workflow run fails; nodeTrace shows every node COMPLETED. The only way to catch it is checking whether the reply is actually grounded in the real data (see Best Practices).
systemPrompt exists for the rare, narrow case where a node’s instructions must stay completely static across every invocation and genuinely need zero context-variable injection that turn — not as a general “split my persona from my data” mechanism.
Gotcha: streamOutput defaults to true
Section titled “Gotcha: streamOutput defaults to true”Unlike most optional booleans in this schema, streamOutput is not off-by-default. The workflow runner dispatches every llm node to its streaming executor unless streamOutput is explicitly set to false. This matches how a direct (non-workflow) chat turn behaves, so a session-facing reply streams sentence-by-sentence to the client by default.
Set streamOutput: false for any llm node whose output isn’t session-facing — a background classification, an extraction step, an evaluation/scoring call. Nothing downstream cares whether that text arrived in one shot or in fragments, and skipping streaming avoids unnecessary per-sentence overhead.
Gotcha: applyVoiceFormatting conflicts with classification prompts
Section titled “Gotcha: applyVoiceFormatting conflicts with classification prompts”applyVoiceFormatting rewrites the system prompt to say things like “speak naturally, keep it to 1–3 sentences, no lists.” That’s exactly wrong for a node whose job is “respond with ONLY one word: billing, technical, or general” — the two instructions actively fight each other, and you’ll see the model wrap its classification in a sentence instead of returning the bare token your router or condition node needs.
Only set applyVoiceFormatting: true on nodes whose outputVar feeds directly into a response node meant to be read aloud (e.g. a TTS-backed voice agent) — never on a classification or extraction node.
Gotcha: extractFirstInteger is a structural fix, not a prompt trick
Section titled “Gotcha: extractFirstInteger is a structural fix, not a prompt trick”Any node used to match a human-given value to an internal numeric ID — a room code to a roomId, a product name to a productId — feeds an LLM’s raw output into a downstream tool or action node’s argsTemplate as a bare, unquoted number. No prompt can guarantee 100% compliance across every model and every call: a well-meaning but off-task reply like “I couldn’t find that room, could you clarify?” breaks argsTemplate’s JSON parsing with a confusing “did not render to valid JSON” error, even after the prompt has been hardened repeatedly.
Setting extractFirstInteger: true sidesteps this: the executor regex-extracts the first integer in the raw response (falling back to 0 — the conventional “no match” sentinel every affected prompt should also document in its own logic) instead of trusting the model to emit only digits. If your workflow needs this pattern, use the flag — don’t keep layering prompt patches on top of it.
Since 2026-09-11, outputVar holds a real JS number, not a digit string. It used to be a string, and that silently broke every graph gating on the sentinel with a condition expression like myVar != 0 or myVar > 0: condition expressions have no type coercion, so "0" != 0 evaluated true and the “no match” sentinel routed straight down the found branch — a live incident fired two outbound writes with an id of 0 before either was rejected by the partner API. If you wrote myVar != "0" or relied on {{#if myVar}} treating the sentinel as falsy, update it to a plain myVar != 0 / myVar > 0 — that’s now what it means. This has no effect on how the value renders in a template: Handlebars renders a number identically to its digit string, so {{myVar}} (bare, for a JSON number position) and "{{myVar}}" (quoted, for an ID!-typed argument) are unchanged.
Gotcha: excludeHistory is for narrow extraction nodes only
Section titled “Gotcha: excludeHistory is for narrow extraction nodes only”Every llm node’s underlying model call receives the session’s full conversationHistory by default, even when its own promptTemplate never references {{conversationHistory}} — the node just doesn’t happen to use what it’s given. That’s usually harmless. It stops being harmless once a session has a few tool-backed turns behind it: if your response nodes render structured envelopes (e.g. a JSON-shaped reply with a type/tool_output field), that JSON becomes part of the history every later llm node sees — including narrow “extract one short value from {{userMessage}}” nodes with no legitimate use for that context. Having real JSON sitting right there in the model’s input measurably increases how often it echoes that shape back instead of the bare value its own prompt asked for.
Set excludeHistory: true on any node whose job is “pull one value out of the current message, nothing else” — a search term, a title, a code. Never set it on a node whose prompt genuinely references {{conversationHistory}} for legitimate multi-turn slot-filling (e.g. resolving a room code or a date the user gave two turns ago) — that node needs the history to do its job.
Gotcha: allowGeneralKnowledgeFallback for RAG nodes with an explicit fallback instruction
Section titled “Gotcha: allowGeneralKnowledgeFallback for RAG nodes with an explicit fallback instruction”The default retrieval-augmentation wrapper tells the model to answer using ONLY the retrieved reference material, and to say so explicitly if the material doesn’t contain the answer — correct for grounded-fact Q&A, where an ungrounded guess would be actively wrong. It fires whenever retrieval returns any chunk, even a poor match — retrieval has no relevance/similarity threshold, it always returns its nearest neighbours.
That’s the wrong default for a node whose own promptTemplate already has an explicit general-knowledge fallback instruction (e.g. a study-kit generator: “if no reference material is available, answer from general knowledge but say so”). Without this flag, the strict wrapper’s “ONLY” directive overrides that instruction the moment retrieval returns even one loosely-related chunk — producing a hard refusal instead of the fallback answer the node’s own prompt asked for.
Set allowGeneralKnowledgeFallback: true on nodes like this. Leave it unset for grounded-fact Q&A nodes where you genuinely want a refusal rather than a guess.
Gotcha: matchAnyOf is a structural fix for classification nodes, same philosophy as extractFirstInteger
Section titled “Gotcha: matchAnyOf is a structural fix for classification nodes, same philosophy as extractFirstInteger”A classification node’s prompt typically says something like “respond with ONLY one of these words: billing, technical, general.” No model — especially a smaller or faster one chosen for cost/latency — can guarantee 100% compliance with that instruction on every call. When it’s ignored, the model answers the user’s actual question in prose instead of emitting the bare classification word. A downstream router node does an exact-string match against that output; a runaway prose reply matches nothing and silently falls through to the router’s defaultTarget, producing a generic fallback reply with no error anywhere to point at why.
Setting matchAnyOf to the full list of valid classification labels sidesteps this: the executor scans the raw response for whichever candidate label appears earliest in the text, and uses that instead of the raw text — so even a model that ignores the “one word only” instruction and answers in full sentences still routes correctly, as long as the correct label happens to appear somewhere in that answer. If no candidate is found at all, the raw text is stored unchanged, so a workflow’s existing defaultTarget/fallback behavior is preserved exactly as if matchAnyOf weren’t set.
Set this on any llm node whose outputVar feeds a router or condition based on a fixed, known set of labels. It has no effect on nodes with free-form or numeric output — use extractFirstInteger for the latter.
Gotcha: imageVars fails the entire run, not just the node, on an incapable provider
Section titled “Gotcha: imageVars fails the entire run, not just the node, on an incapable provider”Added 2026-09-17. imageVars names up to 4 context variables (extras are silently dropped, with a logged warning) whose values get attached as images to this node’s model call, alongside promptTemplate’s text. Each named variable must hold a string that’s either a full data URL (data:image/png;base64,...) or a bare base64-encoded image — a value that’s missing/empty/null on a given turn is a benign skip (not every turn has an attachment for every configured slot), but a present-but-unusable value (wrong type, unparseable, or over the ~5MB per-image cap) throws and fails the node.
Image bytes are sniffed by their own magic-byte signature (JPEG/PNG/GIF/WEBP), never by the ctx variable’s name or any mime type you might separately track — an unrecognized signature fails the node the same way an over-size image does.
Worked example
Section titled “Worked example”A generic appointment-scheduling assistant uses one llm node to extract a requested time slot as free text, and a second, extractFirstInteger-enabled llm node to resolve a human-given slot code to an internal numeric ID before calling a booking tool.
{ "id": "extract_slot_id", "type": "llm", "label": "Match Requested Slot to Internal ID", "config": { "outputVar": "slotId", "promptTemplate": "You are a scheduling assistant's internal matching step. You are not talking to the user — output only a number.\n\nThe user asked for this slot: \"{{userMessage}}\". Available slots are: {{availableSlots}}. Respond with ONLY the numeric id of the best-matching slot from the list, and nothing else. If none match, respond with 0.", "extractFirstInteger": true, "streamOutput": false }, "position": { "x": 200, "y": 120 }}Because extractFirstInteger is set, even a stray reply like “I think slot 4 works best” still resolves slotId to the number 4 instead of breaking a downstream tool node’s argsTemplate. streamOutput: false is set because this node’s output never reaches the user directly.
A second node in the same workflow, this one voice-facing and feeding a response node:
{ "id": "draft_confirmation", "type": "llm", "label": "Draft Booking Confirmation", "config": { "outputVar": "confirmationText", "promptTemplate": "Confirm the appointment was booked for slot {{slotId}}. Be warm and brief.", "applyVoiceFormatting": true }, "position": { "x": 400, "y": 120 }}Here applyVoiceFormatting is appropriate — confirmationText flows straight into a response node meant to be read aloud, not into a router or condition.
Next steps
Section titled “Next steps”- Workflow Overview — node/edge envelope and publishing.
- Condition Node and Router Node — common consumers of an
llmnode’s classification output. Never setapplyVoiceFormattingon a node feeding either of these. - Response Node — the usual consumer of a voice-facing
llmnode’s output. - Image Understanding for an Agent — the full end-to-end walkthrough for
imageVars: getting an attachment into the conversation, the graph shape, and the vision-capable-provider requirement. - Best Practices — templating and LLM-reliability patterns.
- Examples — complete end-to-end workflows.
- Workflows API Reference — mutations for creating and publishing workflows.