What we’re building
We will build a workflow that triages a support ticket. Rules we write decide what is urgent, so those answers never depend on a model. For an ordinary ticket, a model drafts the first reply. If the model fails or is not there, a plain rule sends a template reply instead. We call the first kind a deterministic step: same input, same output. The second is an AI step. Putting both in one graph lets us keep a model on a short leash: it only gets the work that needs judgement.Save the workflow
Create Three things carry the idea:You should see:
triage.json:triage.json
checkis aVALIDATEnode. A ticket without asubjectand abodyleaves through itsinvalidport.routeis aBRANCHnode. Each key incasesis a yes/no condition, and the value is the port the message leaves through. Anything else goes todefaultPort.draftis anLLM_CALLnode. WithfailureModeset toCONTINUEandrouteByStatuson, a failed call does not fail the run: the message leaves throughfailureinstead ofsuccess.
Stuck? A400that mentionsLLM_CALLorVALIDATEas an unknown type means this server was built without that plugin.curl http://localhost:9858/api/pluginslists the types it has.
Add a small helper
We will start three runs, so we put the repeated part in a shell function. It starts a run with the ticket you pass, waits until the run is no longer With an API key on the server, add
RUNNING, then prints the result and the status of every node.-H "X-API-Key: <your key>" to the three curl calls.Send an urgent ticket: only rules run
draft was skipped, so no model was called and no tokens were spent.Stuck? If the helper hangs,jqis missing or the server needs a key: run thecurlstart call by hand and read its answer.
Send an invalid ticket: the check stops it
This ticket has no You should see the ticket arrive unchanged at the The run itself is
body:rejected terminal, and every later node skipped:SUCCESS: setting a bad ticket aside is something the workflow was built to do.Send a normal ticket: the AI step runs
draft could reach a model, and both answers are correct. We called Ollama with llama3.2, which is the default provider and model in the file.- A model answered
- No model available
With a local Ollama that has
llama3.2 (ollama pull llama3.2), draft succeeds and the model’s text is in reply. Your wording will differ:SUCCESS with an answer the customer can be sent. decidedBy records who made the decision, so you can always tell a model’s answer from a rule’s.Stuck? If the run itself endsFAILURE, thedraftnode failed before the call was made, for example because it is configured with a key that is not set (decrypted:<key>reads the environment variableYUKTA_SECRET_<KEY>).curl http://localhost:9858/api/executions/$EXECnames the failed node.
What you just did
We put two kinds of work in one graph. Rules decided what is urgent and what is invalid, the same way every time. A model got only the step that needs wording, and we gave it a way out, so a missing or failing model costs us a template reply and not a failed run. Every answer says whether a rule or a model made it.Where to go next
Let an assistant build a workflow
Next tutorial: have Claude Code write and run a workflow like this one.
Why one graph holds both
Where AI belongs in a workflow, and where it does not.
Call an LLM from a workflow
Providers, secrets, retries and the options of
LLM_CALL.Set token budgets
Cap what a model may spend in one run.