{"slug": "how-to-fall-back-to-default-logic-when-llm-output-is-unsatisfactory", "title": "How to Fall Back to Default Logic When LLM Output is Unsatisfactory", "summary": "A guide on handling unsatisfactory LLM output argues that systems should validate responses against a strict schema using Pydantic's Literal types and ConfigDict(strict=True, extra=\"forbid\"), since without them values like \"priority\": \"urgent-ish\" pass validation and break downstream routing. The guide states that bad output surfaces in three ways — API timeouts, rate limits or 500 errors; malformed or schema-invalid JSON; and valid-but-wrong answers such as marking a chargeback threat as low priority — and that each level needs its own check because \"you cannot fall back on what you cannot detect.\" It recommends a keyword-based fallback that returns a usable default (category, \"priority\": \"medium\", \"needs_human\": True) for classification, extraction and routing tasks, while noting open-ended writing has no such fallback.", "body_md": "A support team wires up a small workflow. Every incoming email goes to an LLM, which returns:\n\n```\n{ \"category\": \"billing\", \"priority\": \"high\", \"needs_human\": false }\n```\n\nThe rest of the workflow reads those fields and routes the ticket. It runs fine all week. Then one morning it doesn’t: a request comes back as a friendly paragraph instead of JSON, another times out, and a third returns valid JSON with \"priority\": \"urgent-ish\" which parses cleanly but breaks the router. Tickets stop moving, and nobody finds out until a customer escalates.\n\nEveryone will tell you to add a fallback. But this leaves two questions: when should the fallback kick in, and what should it do instead?\n\nIt is not that complex; you already have a fallback. Right now it is “crash, or pass something broken downstream.” The job is not to add one. It is to replace a bad one with a better one. This guide works through both questions using the ticket triage workflow above.\n\nBefore you can pick a fallback, your system needs a way to tell good output from bad. Bad output shows up in three ways:\n\nThe API timed out, hit a rate limit, or returned a 500. This one is easy, because your code already knows. The call raised an error. Most people think of this case first. It is the least of your problems.\n\nThe model returned a paragraph instead of JSON. Or valid JSON with a field missing. Or \"urgent-ish\" in a field where you allow three values.\n\nSchema validation catches all of these. Define the shape you expect, then check every response against it:\n\n``` python\nfrom pydantic import BaseModel, ConfigDictfrom typing import Literal\nclass Triage(BaseModel):    model_config = ConfigDict(strict=True, extra=\"forbid\")    category: Literal[\"billing\", \"technical\", \"account\", \"general\"]    priority: Literal[\"low\", \"medium\", \"high\"]    needs_human: bool\ntriage = Triage.model_validate_json(raw_output)\n```\n\nWithout the Literal types, \"urgent-ish\" sails through and breaks something three steps later, far from where the problem started. With them, you get a clean error the moment it arrives.\n\nThe model refused to answer, or it read an angry chargeback threat and marked it low priority. Nothing is malformed, so validation passes, and the workflow carries on with a bad answer.\n\nThis is the expensive one, because catching it needs rules from your own business. Here is one you can actually check: if the email mentions a chargeback or a lawyer and the model returned low priority, treat that as a failure.\n\nEvery level needs its own check. A timeout surfaces through your error handler, a bad schema through your validator, and a wrong priority through your own business rules. Nothing on that list is caught automatically: *you cannot fall back on what you cannot detect.*\n\nHere are your options, from cheapest to most expensive.\n\nNow, the question is: how do you choose between them? Here is my rule of thumb: a fallback only helps if it fails differently from what it replaces.\n\nLet’s run the options above through it:\n\n``` python\ndef triage_fallback(email_text):    text = email_text.lower()\nif any(word in text for word in [\"refund\", \"invoice\", \"charge\"]):        category = \"billing\"    elif any(word in text for word in [\"login\", \"error\", \"crash\"]):        category = \"technical\"    else:        category = \"general\"\nreturn {        \"category\": category,        \"priority\": \"medium\",        \"needs_human\": True,    }\n```\n\nThose keyword lists are deliberately basic, and a real one would be tuned against your own tickets. Even so, this is worse than the model on a good day and much better than the model on a bad one, because it returns something the workflow can use. The ticket reaches a human queue instead of vanishing.\n\nTicket triage has an obvious fallback because the output is a small set of choices. Classification, extraction, and routing all work this way. Open-ended writing does not. If the model fails to draft a good reply to a customer, no keyword rule will draft one for you. Your options stop at retrying, escalating, or honestly saying you couldn’t do it.\n\nOne caveat, though: retrying gets risky once the first attempt already did something. If it created a ticket before failing, a second attempt creates a second ticket. Retry the model call, not the actions it already took.\n\nIn code, you build most of this yourself. In n8n, you configure most of it. One thing to get right first: the model here never picks a tool, so this is a [Basic LLM Chain](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm) node, not an [AI Agent](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent) node, which requires a tool.\n\nIn your code In n8n try/except around the call [On Error, Continue (using error output)](https://docs.n8n.io/build/understand-workflows/workflow-components/work-with-nodes) Retrying a failed call [Retry on Fail](https://docs.n8n.io/build/understand-workflows/workflow-components/work-with-nodes) The Pydantic model [Structured Output Parser](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.outputparserstructured/) Retry with the error fed back [Auto-fixing Output Parser](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.outputparserautofixing/) Backup provider Second chain on the error branch Your business rules [Switch](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.switch/) or [Code](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/) node\n\nOpen the Basic LLM Chain node’s settings and turn on Retry on Fail. n8n allows up to 5 tries with up to 5 seconds between them, and that wait does not grow. That helps with a temporary failure, but not a long rate-limit window.\n\nFor those cases, [n8n’s rate-limit guidance](https://docs.n8n.io/integrations/builtin/handle-rate-limits) recommends controlling the pace instead: use a [Loop Over Items](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.splitinbatches/) node to batch requests, and a [Wait](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/) node to space them out.\n\nFor a provider outage, set On Error to Continue (using error output) and route that error output into a second Basic LLM Chain node, with a different provider’s chat model attached. n8n has a [template](https://n8n.io/workflows/5160-build-resilient-ai-workflows-with-automatic-gpt-and-gemini-failover-chain/) that demonstrates this kind of failover.\n\nTurn on Require Specific Output Format and connect a Structured Output Parser. Define the schema you expect.\n\nFor our example, that means restricting priority to low, medium, or high, not just saying it is a string. If you generate the schema from a JSON example, n8n reads the field names and types but ignores the example values, so use a JSON Schema with enum values when the values matter.\n\nTo let n8n attempt a repair first, wrap the Structured Output Parser in an Auto-fixing Output Parser. When parsing fails, it sends the bad output and the parsing error to another LLM and asks it to fix them.\n\nApply our test here: the auto-fixer is another model call, so it can fail in some of the same ways. If the repair still fails, send the error output to your plain-code fallback.\n\nA valid response can still be a bad one. n8n calls the step successful as long as the output parses, even if your business rules disagree with the answer.\n\nAdd a Switch or Code node after the chain to catch those cases. If the email mentions a chargeback or lawyer but the model says priority = low, send it down the same fallback branch.\n\nIf those rules live in a Code node, handle its errors too. It can fail like any other node, and an unhandled error there stops the workflow it is meant to protect.\n\nA fallback nobody looks at is a slow leak. You can inspect executions to see which nodes and branches ran. To measure the rate reliably, record it yourself: [custom execution data](https://docs.n8n.io/build/understand-workflows/understand-executions/customize-executions-data), available on Pro, Enterprise, and registered Community plans, stores string pairs like fallback_used = “true” that you can filter executions by.\n\nFor failures that escape every branch, set an error workflow starting with the [Error Trigger](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.errortrigger/) node to alert your team.\n\nA fallback rate that suddenly climbs means something changed upstream. You want to hear that from your workflow, not from a customer.\n\n[How to Fall Back to Default Logic When LLM Output is Unsatisfactory](https://pub.towardsai.net/how-to-fall-back-to-default-logic-when-llm-output-is-unsatisfactory-e5cdb3b63964) was originally published in [Towards AI](https://pub.towardsai.net) on Medium, where people are continuing the conversation by highlighting and responding to this story.", "url": "https://wpnews.pro/news/how-to-fall-back-to-default-logic-when-llm-output-is-unsatisfactory", "canonical_source": "https://pub.towardsai.net/how-to-fall-back-to-default-logic-when-llm-output-is-unsatisfactory-e5cdb3b63964?source=rss----98111c9905da---4", "published_at": "2026-09-25 05:29:02+00:00", "updated_at": "2026-09-25 05:59:08.470426+00:00", "lang": "en", "topics": ["large-language-models", "ai-agents", "ai-tools", "developer-tools"], "entities": ["Pydantic"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/how-to-fall-back-to-default-logic-when-llm-output-is-unsatisfactory", "markdown": "https://wpnews.pro/news/how-to-fall-back-to-default-logic-when-llm-output-is-unsatisfactory.md", "text": "https://wpnews.pro/news/how-to-fall-back-to-default-logic-when-llm-output-is-unsatisfactory.txt", "jsonld": "https://wpnews.pro/news/how-to-fall-back-to-default-logic-when-llm-output-is-unsatisfactory.jsonld"}}