Skip to content
Expedify
AI & the Knowledge Base

Module · The AI nodes

LLM Integration

Lesson 1 of 6 · 11 min

Most AI work in a workflow is not a conversation. It is one question asked once: what is this message about, write a summary of that, pull the three fields out of this text. There is nothing to decide and nothing to look up — just an input, a prompt, and an answer.

That is this node. One call, no tools, no back-and-forth. If the task needs the model to go and find something, or to try, check and try again, that is the Core Agent in the next lesson. If the task is a single transformation, use this — it is cheaper, faster, and far easier to reason about when it goes wrong.

It is also where you learn to write a prompt, because a prompt is all this node is.

The fields

Eight of seventeen. Most of the rest are the Prompt Builder's structured-section storage.

integration_id

What it holds
LLM integration to use

model

What it holds
LLM model to use (dropdown selection) Defaults to gpt-4.1-mini.

temperature

What it holds
Sampling temperature (0.0 to 2.0) Defaults to 0.2.

system_prompt

What it holds
System prompt to set context (compiled from prompt_sections when prompt_mode='builder')

prompt_template

What it holds
Prompt template with variable substitution Defaults to {{message}}.

response_format

What it holds
Response format configuration (e.g., JSON mode)

timeout

What it holds
Request timeout in seconds Defaults to 60.

max_retries

What it holds
Maximum number of retries on failure Defaults to 2.

Two fields hold the prompt and they do different jobs. system_prompt is the standing instruction — who the model is, what it is for, what the rules are. prompt_template is the thing being asked about this time, and it is where the variables go.

Temperature is documented as 0.2 and defaults to 0.7 when the field is absent. The panel writes 0.2, so a node you configured in the builder behaves as documented. A node created through the API or by an agent, without the field set, runs at 0.7 — noticeably less predictable on exactly the classification tasks this node is best at. Set temperature explicitly and the ambiguity goes away.

Writing the prompt

You can write the prompt as one block of text or build it from labelled sections. The sections are not decoration — they are the difference between a prompt you can debug and a paragraph you keep rewriting. Five of them carry almost all the weight:

What goes in it
Who the model is. One sentence. It sets vocabulary and assumptions more than people expect.

What goes in it
What this call is for, in the singular. If you need “and also”, you probably want two nodes.

What goes in it
The rules, as a list. This is where the enumerated choices go — classify as exactly one of these five.

What goes in it
What it must not do. Not invent an order number. Not promise a date. Under eighty words.

What goes in it
The shape you want back. With JSON mode on, name the exact keys.

Constraints are the section beginners leave out and then need. A model asked to draft a reply will cheerfully invent a delivery date, because a helpful reply contains one. Nothing in the objective forbade it. Every unwanted behaviour you find in testing becomes a line in this section.

A worked example

An inbound message is classified and a reply is drafted in a single call. The workflow then branches on what the model decided.

One call, structured output, then a decision

JSON mode is what makes the branch below it possible.

YesNo

Scroll for all 5 steps →

Read that prompt as five decisions rather than as prose. The intent list is closed — five values, no “or similar” — so the output is something a Condition can compare. Urgency has an explicit rule for when high applies, because “use your judgement” produces a different judgement every run. And the constraints name the two things this model would otherwise do wrong.

JSON mode is what turns an answer into a decision. With response_format set to JSON, the response is parsed and each key becomes readable as {{alias.parsed.<key>}} — which is how the condition below reaches urgency without any string matching. Without it, you would have one blob of prose and no way to branch on it.

What comes back

{{alias.output}}

What you get
The model's text. In JSON mode this is the raw JSON string.

{{alias.parsed}}

What you get
The parsed object — and {{alias.parsed.intent}} for a field. Only populated in JSON mode.

{{alias.tokens_used}}

What you get
What the call cost, with prompt and completion counts alongside. Log it while you are tuning.

{{alias.model}} · {{alias.runtime_ms}}

What you get
Which model actually answered, and how long it took.

What breaks

Reaching into parsed without JSON mode fails in the worst way. The field is only filled when response_format is set to JSON. Reference {{alias.parsed.intent}} without it and you get the literal text <llmintegration_1.parsed.intent not found> — pasted into whatever came next, including a message to a customer. If you are branching on a model's answer, JSON mode is not optional.

JSON mode guarantees valid JSON, not the keys you asked for. The model will return well-formed JSON reliably. Whether it contains intent or category depends on your prompt saying so, exactly, in the output-format section — and on it being the only place a key name appears. Name the keys once, precisely.

A closed list is only closed if you enumerate it. “Classify the intent” gets you whatever words the model likes, differently each time, and every downstream Condition slowly stops matching. “Exactly one of: delivery_status, billing, complaint, sales, other” gets you one of five. Add other to every list — without it the model forces a bad fit rather than admitting the message is none of them.

Retries are not free and not idempotent in effect. max_retries defaults to 2, so a flaky provider costs three calls' worth of tokens for one answer, and each attempt may answer slightly differently. That is fine here, and it is worth remembering when a bill looks larger than the number of runs suggests.

Try it

  1. Build the smallest version: a trigger, this node with a one-line system_prompt, and prompt_template set to {{trigger_1.message}}. Read {{alias.output}}.
  2. Now ask it to classify into three named categories, run the same message five times, and note whether the answer is stable. Then add temperature of 0 and do it again.
  3. Turn on JSON mode, name your keys in the output-format section, and read {{alias.parsed}}. Then turn JSON mode off and reference {{alias.parsed.intent}} — the angle brackets are the failure worth seeing once.
  4. Ask it for a reply without a constraints section and count how many drafts promise a date. Add the constraint and count again.
  5. Finally set temperature to 1.5 and run the classifier. That is what the undocumented default drifts toward, and why setting it explicitly matters.

Next: Core Agent — the same model, given tools and permission to use them until the job is done.