How to Stop Paying for an LLM Call on Every Automation Run
Stop paying for an LLM call on every automation run: audit your nodes, split decisions from generation, pick the right option per node, then do the math.
Every time your automation fires, it calls an LLM. At a hundred runs a day that is a rounding error. At a hundred thousand, the invoice scales with your traffic whether or not the work needed a language model, and much of it often does not: many LLM calls in automations return one label from a fixed set. "Billing / technical / sales." "Urgent? yes/no." "Which tags apply." Paying flagship chat prices for multiple-choice answers is a common source of waste in these systems, often as several separate calls about the same input.
This guide covers how to stop paying for LLM calls on every automation run with one lever: moving decision-shaped steps off per-call LLMs. Audit what you are paying for, tell decisions from generation, pick the right option for each decision node, validate, and do the math. It is useful even if you never install the self-hosted option it describes. For the broader cost levers (caching, batching, prompt trimming, model tiers), see reducing OpenAI API costs.
Step 1: Find every LLM call and what it costs
You cannot cut what you cannot see. One afternoon, one table:
- List every node that calls an LLM, per workflow: OpenAI, Anthropic, AI Agent and HTTP Request nodes in n8n; the equivalent modules in Make and Zapier. Note which model each one uses.
- Get call volume per node from execution history, and cost per node from the provider usage dashboards, split by API key or project where possible.
- Record the output type of each node: label, tags, yes/no, score, or text. That single column is the whole method for Step 2.
| Workflow | Node | Model | Calls/month | Avg tokens in/out | Est. cost/month | Output type |
|---|---|---|---|---|---|---|
| support-in | classify | label | ||||
| support-in | tags | tags | ||||
| support-in | summarize | text | ||||
| billing | urgent? | yes/no |
Fill it from real data, not estimates; the estimates are what got you here.
Step 2: Classify each node: decision vs. generation
It's a decision if the useful output is one choice from a known list, several tags from a known list, a yes/no, or a rating, and any free text is thrown away or parsed into a label. The test: could you write the answer key in advance? If yes, it is a decision.
It's generation if the output text is the product. These stay on an LLM; the only question is which tier.
Examples that show where the boundary sits:
- "Which team handles this?" → decision.
- "Which of these 12 topics apply?" → decision (multi-label).
- "Does this message contain a refund request?" → decision (yes/no).
- "How severe is this bug, low/normal/critical?" → decision (rating).
- "Draft a reply apologizing for the outage." → generation.
- "Summarize this thread for the agent." → generation.
- "Extract the order number." → borderline: if the format is predictable it is a lookup or a small decision; if truly open-ended, generation.
- "Is this customer angry?" → decision (a yes/no you can threshold).
Decisions are the movable nodes. Generation is not going anywhere, and that is fine.
Step 3: Choose the option for each decision node
Four options, one paragraph each:
Keep on a large model. For decisions needing outside knowledge, long multi-step reasoning, or very large and constantly changing option sets. Rare, but real. If you are unsure, your labeled set will tell you.
Move to a cheap chat tier (GPT-6 Luna, Claude Haiku). Lowest friction if you are staying on chat APIs: same SDK, smaller model. Downsides: per-token billing including generated output, one decision per prompt unless you build structured output yourself, and no per-option probabilities unless the API exposes log-probabilities. (You can fold multiple questions into one structured-output prompt; it costs output tokens and gives no probabilities, so treat it as the conservative case in any math.)
Move to a hosted decision model (GPT-6 Luna Decisions). A Decisions API returning per-option probabilities for many questions per request (specs and price: see GPT-6 Luna Decisions alternatives, and check OpenAI's docs on the day). You stop paying for generated tokens because nothing is generated. Downsides: data leaves your servers, per-input-token billing, provider-managed versions.
Replace with Saina Helm (self-hosted decision model). A decision model you run yourself: flat hosting cost instead of per-token, data stays in-house, native multi-label questions, and a per-question confidence threshold whose rejected answers go to a fallback branch. This is the worked example below.
Step 4: The Helm worked example
Helm scores every option you pass and returns a probability for each instead of generating text, and one call can answer several typed questions about the same input (full specs).
Before: a support-ticket workflow makes three separate calls to a flagship chat model: route to a team, apply tags, set priority. Three prompts in, three answers out, three invoice lines per ticket.
After: one /v1/ask call with three questions:
import os
from saina import Saina, SainaError
client = Saina('https://helm.internal.example', api_key=os.environ['SAINA_API_KEY'])
result = client.ask(
model='saina-helm-2-0.8b',
state=ticket_text,
mode='decision',
threshold=0.8,
questions={
'team': {
'type': 'single_choice',
'question': 'Which team should handle this?',
'options': {'billing': 'Charges, invoices, refunds',
'technical': 'Outages, errors, account access',
'other': 'Anything else'},
},
'tags': {
'type': 'multi_choice',
'question': 'Which topics apply?',
'threshold': 0.5, # per-tag; an example to tune
'options': {'refund': 'Money back requests',
'outage': 'Service down or degraded',
'access': 'Login or permission trouble'},
},
'priority': {
'type': 'rating',
'question': 'How urgent is this?',
'levels': ['low: routine', 'normal: same-day attention',
'high: drop other work'],
},
},
)
team = result['answers']['team']['selection'] # None if rejected
tags = result['answers']['tags']['selections'] # labels at or above 0.5
level = result['answers']['priority']['level'] # 0, 1 or 2, or None
The rating returns level as a zero-based index into levels, with probabilities keyed "0", "1", "2".
Confidence handling: for single-choice and yes/no, act on the top option only when it clears the threshold (and, optionally, a minimum margin over the runner-up). Rejected answers come back with no selection and a reason (below_threshold, below_margin, tie), ready for a fallback branch: human review, or escalation to an LLM. Multi-label questions apply the threshold to each tag separately (no margin); ratings ignore the margin. Inference errors are different: the SDK raises SainaError, so catch it and route it the same way. The 0.8 default is a starting point; tune it on your data.
Setup: the example targets Saina Helm 2 0.8B with the pinned saina 0.6.1 Python package, set up as in the release-pinned walkthrough, with TLS and auth in front. Start with at least 4 GB RAM on a CPU and measure memory and latency on your own inputs, as the self-hosting guide describes. In n8n, call /v1/ask from an HTTP Request node with model saina-helm-2-0.8b and branch on each answer's reason; the community node currently hard-codes the Helm 1 model ID. Scope note: Helm replaces the decision node; the rest of the workflow stays as it is. Generation steps and other chat calls stay where they are.
Step 5: Validate before cutting over
Shadow mode, one week, per node:
- The new option runs alongside the existing node on real traffic; both answers logged; nothing routes on the new one yet.
- Disagreements sampled and reviewed: classify each as new-system wrong, old-system wrong, or genuinely ambiguous.
- Fallback rate acceptable; the review branch is staffed.
- Rollback path tested (feature flag or duplicate branch).
Step 6: Do the math before you stop paying for an LLM call on every automation run
For the same workload, compare four columns. Plug in current prices from each provider's official pricing page (note the date), your own token counts from Step 1, and your own volume. For self-hosting, use your hosting provider's price for a VM with at least 4 GB RAM, and write it down as a labeled, dated assumption. This post prints no prices: this market reprices often, and the only numbers worth using are the ones on each provider's official pricing page on the day you compare.
| Column | Calls | Cost formula | Monthly at your volume |
|---|---|---|---|
| Flagship chat | 3 per item | tokens × rates | your number |
| Cheap chat tier | 3 per item (conservative; see Step 3) | tokens × rates | your number |
| Hosted decision API | 1 per item | input tokens × rate | your number |
| Helm (self-hosted) | 1 per item | fixed hosting | your number |
Expect the biggest saving to come from moving decisions off flagship and mid tiers. A hosted decision model can cost less than hosting Helm yourself at moderate volumes; run your numbers. Helm's cost case is a flat cost at very high volume; its main case is data control. Self-hosting has compute and maintenance costs too: there is no per-token fee for these decisions, but the server is not free.
When not to use Helm
Open-ended outputs (the text is the product). Decisions needing outside knowledge or multi-step reasoning. Huge or constantly changing option sets. Workflows where data can leave your servers and a hosted decision model is simply less work. In all of those, the right answer is something else, including, often, "keep the chat model you already have".
Related posts
- Reducing OpenAI API costs (pillar)
- LLM decision making
- Cheap AI classification
- GPT-6 Luna Decisions alternatives
- LLM classification failures
- Fixing GPT routing problems
Final checklist
- Audit table complete: every LLM node, volume, tokens, cost, output type.
- Every node classified decision vs. generation, with the answer-key test.
- Decision nodes assigned: keep / cheap tier / hosted decision / Helm.
- Shadow validation done per changed node, with disagreement review.
- Math filled from dated prices with your volumes; hosting assumption stated.
- Generation steps deliberately left on chat models.