Skip to main content

How to Make a Non-Deterministic AI System Reliable in Production

Reliability in LLM applications comes from the system around the model, not a better prompt. Here are the concrete patterns I use in production: structured outputs, schema validation, idempotency, retries, fallbacks, evals, and observability.

Insights
12m read
#AIArchitecture#LLMProduction#AIEngineering#GenerativeAI#MLOps
How to Make a Non-Deterministic AI System Reliable in Production - Featured blog post image
Mahmoud Zalt

1:1 Mentor

Are you a software engineer moving into AI?

Let's have a call. I'll help you modernize your skills and learn the tools, systems, and architecture behind real AI products. One session or ongoing.

Hire AI Employees

Hire AI Employees that work 24/7. No code.

How to Make a Non-Deterministic LLM System Reliable in Production

You make a non-deterministic LLM application reliable by building a deterministic system around the model. The model itself will never be fully predictable, but your pipeline can be: enforce structured output contracts, validate every response at a schema boundary, retry on failure with backoff, fall back gracefully when retries are exhausted, and make every side-effectful operation idempotent so re-runs are safe.

I am Mahmoud Zalt, an independent AI systems architect with 16+ years of production experience since 2010. I am the author of Porto SAP, an architecture pattern for keeping complex systems predictable, and that same obsession with structure is what I bring to Sista AI, the company I founded, where I have spent a year making non-deterministic agents behave reliably in production. I work with engineering teams as an AI architecture advisor to design exactly these kinds of production-grade AI systems. What follows is the full playbook I use.

Why 'Better Prompts' Do Not Solve Reliability

The first instinct when an LLM produces bad output is to fix the prompt. That instinct is wrong, or at least incomplete. Prompts influence probability distributions. They do not enforce contracts. A model that returns valid JSON 98% of the time will fail roughly 1 in 50 calls in production. At 10,000 calls per day, that is 200 silent failures.

The same logic applies to temperature, top-p, and seed parameters. Setting temperature=0 gives you near-deterministic outputs on the same model version, but your provider updates model weights on their own schedule. What worked last month may not work next month. You cannot outsource reliability to the model layer.

Reliability is an architectural property. It lives in validation, retries, fallbacks, and observability. The model is just one unreliable component inside a reliable wrapper, the same way you would treat any third-party API that occasionally returns garbage.

Structured Outputs and Schema Validation: The First Line of Defense

The single most impactful change you can make to an LLM pipeline is forcing structured output and validating it against a strict schema before any downstream code touches it. Every major provider now supports this natively.

Use provider-native structured output modes

OpenAI's response_format: { type: 'json_schema', json_schema: { strict: true, schema: {...} } } guarantees the model output conforms to your JSON Schema before it is returned to you. Anthropic's tool-use mode forces structured responses via the tool input schema. Google Gemini supports response_mime_type: 'application/json' with a schema. Always use the strictest mode your provider offers. Do not parse free-form text when you can get a schema-constrained response.

Validate at the boundary with Zod or Pydantic

Even with provider-level constraints, always re-validate in your own code. Model providers have bugs and schema enforcement is not perfect across all edge cases. A Zod schema in TypeScript or a Pydantic model in Python adds one line of protection that will catch the cases the provider misses.

// TypeScript example
const ExtractedLeadSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  intent: z.enum(['buy', 'explore', 'support']),
  confidence: z.number().min(0).max(1),
});

const raw = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [...],
  response_format: {
    type: 'json_schema',
    json_schema: { strict: true, schema: zodToJsonSchema(ExtractedLeadSchema) },
  },
});

const parsed = ExtractedLeadSchema.safeParse(
  JSON.parse(raw.choices[0].message.content)
);
if (!parsed.success) {
  // handle validation failure, do not proceed
}

If safeParse fails, you have a structured error you can log, alert on, and retry with. If you had used free-form text, you would have a string and a guess.

Retries, Backoff, and Fallbacks: Handling Inevitable Failures

Validation failures and provider errors happen. Your pipeline needs a defined policy for each failure mode before you ship, not after your first incident.

Retry policy for transient failures

Transient failures include rate limits (HTTP 429), server errors (HTTP 500, 503), and network timeouts. Use exponential backoff with jitter. A simple policy: 3 attempts, initial delay 1s, multiplier 2, jitter +/-20%. Most transient errors resolve within the first retry.

Retry policy for validation failures

When the model returns structurally valid JSON but your Pydantic/Zod schema rejects it (wrong enum value, missing field, out-of-range number), you have a different problem. One good pattern: on the first validation failure, re-send the original prompt plus the validation error message and ask the model to correct its output. This works well because the model can often self-correct given explicit schema feedback. Limit this to one self-correction retry before escalating to a fallback.

Fallback chain

Define a fallback chain before launch. A typical production chain looks like this:

  • Primary: GPT-4o with strict JSON schema mode.
  • Fallback 1 (on 2 consecutive failures): Claude Sonnet with tool-use mode, same schema.
  • Fallback 2 (on provider outage): A simpler deterministic rule-based classifier that covers the 80% case with lower quality but 100% uptime.
  • Final fallback: Queue the request for async human review and return a graceful 'processing' state to the caller.

The key insight here is that 'I could not process this right now' is a valid and honest product state. It is always better than silently returning garbage or crashing.

Idempotency: Making Retries Safe

Retries are only safe if operations are idempotent. This is the most commonly overlooked reliability concern in LLM systems, especially when the pipeline involves tool calls, database writes, email sends, or any other side effect.

The rule is simple: every operation triggered by or downstream of an LLM response must be idempotent. If the same LLM call is retried (due to a timeout, a validation failure, or a deployment restart), re-executing the downstream action must produce the same result, not a duplicate.

Concrete implementation pattern

Assign a deterministic idempotencyKey to every LLM pipeline invocation at the entry point, before any model call happens. Derive it from the input, not from a random UUID. A hash of the user ID plus the request payload works well. Pass this key through every step. Before executing any side-effectful operation, check whether that key has already been committed. If yes, skip and return the cached result.

// Derive key from stable inputs
const idempotencyKey = sha256(`${userId}:${requestPayload}`);

// Check before acting
const existing = await db.pipeline_results.findOne({ idempotencyKey });
if (existing) return existing.result;

// Run pipeline, then persist result atomically
const result = await runLLMPipeline(...);
await db.pipeline_results.insertOne({ idempotencyKey, result, createdAt: new Date() });
return result;

This pattern also gives you a free audit log of every pipeline execution, which is useful for debugging and for the evals layer covered below.

Tool Calling and MCP: Reliability at the Action Layer

LLM tool calling (function calling, MCP tools) introduces a second non-determinism surface: not just what the model says, but what actions it takes. A model that randomly calls the wrong tool or passes malformed arguments to a tool can cause real damage in production.

The patterns that matter here:

  • Narrow tool schemas. Every parameter should have a tight description, an enum where applicable, and a clear indication of what is optional. Ambiguous schemas produce ambiguous invocations.
  • Validate tool call arguments. Treat the model-generated tool call arguments exactly like user input. Parse them through your schema validator before executing the tool. Never pass raw model arguments directly to a database query, a file system call, or an external API.
  • Confirm before destructive actions. Any tool that deletes, updates, sends, or charges should require explicit human confirmation unless the system is fully internal and low-stakes. Build a 'pending action' state into your data model from the start.
  • Log every tool invocation with inputs and outputs. This is your audit trail and your primary debugging surface when something goes wrong.

MCP (Model Context Protocol) follows the same rules. Each MCP tool is a contract. Validate inputs before execution, validate outputs before returning them to the model, and cap the number of tool-use rounds per invocation to prevent infinite loops (a hard limit of 10-15 rounds is a sensible default for most workflows).

Evals and Observability: You Cannot Improve What You Do Not Measure

Reliability in production is not a one-time achievement, it is an ongoing monitoring discipline. LLM behavior drifts as providers update models, as your prompt changes, and as real-world inputs diverge from your development assumptions.

Structured logging for every LLM call

Log the following for every model invocation as a structured JSON record: timestamp, model ID, provider, prompt tokens, completion tokens, latency (ms), success/failure flag, validation pass/fail, the top-level intent or pipeline name, and the idempotency key. Do not log raw prompt content in high-volume systems (cost and privacy), but do log a content hash so you can retrieve the full record when debugging.

Eval suite on a golden dataset

Maintain a golden dataset of 50 to 200 representative inputs with expected outputs. Run your full pipeline against this dataset on every deploy and on a weekly schedule. Track pass rates over time. A drop of more than 5 percentage points in a weekly eval run is a signal worth investigating before it becomes a production incident.

Live failure monitoring

Set up alerts on: validation failure rate above 2%, retry rate above 5%, fallback activation more than 1% of calls, and p95 latency above your SLA. These thresholds will vary by use case, but having them defined and monitored means you find out about model drift from your dashboard, not from a user complaint.

What teams get wrong

Most teams log the final output but not the intermediate steps. When a multi-step pipeline fails, they have no way to know whether the failure happened at extraction, classification, tool-calling, or output formatting. Log every step, not just the result. The storage cost is negligible compared to the debugging time you save.

Human-in-the-Loop: Where to Put the Manual Checkpoint

Fully automated LLM pipelines are appropriate for low-stakes, reversible actions. For anything involving money, legal documents, customer-facing communications, or irreversible state changes, you need a human checkpoint and you need to design it into the architecture from the beginning, not bolt it on after an incident.

A practical framework for deciding where to put the checkpoint:

Action TypeReversibleStakesRecommended Approach
Read / summarizeYesLowFully automated, eval-monitored
Draft content for human sendYesMediumAutomated generation, human approves before send
Write to internal DBYes (with audit log)MediumAutomated with confidence threshold gate
External API call (charge, send, delete)NoHighRequire explicit human confirmation
Legal or compliance outputNoHighLLM drafts, human reviews and signs off

The confidence threshold gate is worth elaborating. If your model returns a confidence field (and it should, as part of your structured output schema), you can route low-confidence responses to a human review queue automatically. High-confidence responses proceed. This gives you the speed of automation on the easy cases and the safety of human judgment on the hard ones.

Cost and Security: The Constraints That Shape Everything

Cost

Non-determinism creates hidden cost amplifiers. Retries cost tokens. Validation failures that trigger self-correction rounds cost twice the tokens. A pipeline that fails 5% of the time and retries once adds 5% to your token bill automatically. Design your retry and self-correction policy with token cost in mind: set hard caps on tokens per pipeline run, cache LLM responses for identical inputs where semantically appropriate, and use smaller models for steps that do not require frontier capability (classification, extraction, routing).

A practical split: use a small model (Haiku, GPT-4o mini) for intent classification and routing. Reserve the frontier model for the generation step that actually requires it. On typical pipelines, this reduces token cost by 40 to 60% with no meaningful quality loss on the cheap steps.

Security

LLM inputs are user-controlled in most applications. Treat prompt injection as a real threat, not a theoretical one. Sanitize user content before including it in system prompts. Never allow user input to modify system instructions directly. When using tool calling, apply the principle of least privilege: each tool should only be callable by the pipeline stages that legitimately need it. Audit tool call logs for anomalous patterns (unusual argument values, unusual call frequency) as part of your security monitoring.

For systems that handle PII, ensure that neither prompts nor completions containing PII are logged to third-party observability platforms without explicit data processing agreements in place.

Frequently Asked Questions

Does setting temperature to 0 make an LLM deterministic?

Near-deterministic on a fixed model version, yes. But providers update model weights without always versioning them. The output for the same prompt and temperature will drift over time. Temperature 0 reduces variance within a session, it does not eliminate drift across weeks and months. You still need schema validation and evals.

How many retries should an LLM pipeline attempt before giving up?

Three attempts total (one original plus two retries) is the right default for most pipelines. For transient provider errors, use exponential backoff: 1s, 2s, 4s. For validation failures, one self-correction retry is usually enough. If the pipeline still fails after three attempts, route to fallback or human review. More retries add latency and cost without proportional reliability gains.

Should I use streaming responses in a production LLM pipeline?

Only when the end-user experience requires it (chat interfaces, progressive rendering). For backend pipelines that extract, classify, or transform data, non-streaming is strictly better: you can validate the complete response against your schema before doing anything with it. Streaming makes validation harder and should be avoided in headless pipeline stages.

How do I handle LLM model deprecations without breaking my production system?

Pin to specific model versions in production (e.g., gpt-4o-2024-11-20 not gpt-4o). Track provider deprecation announcements and run your eval suite against the replacement model before switching. Treat a model migration the same way you would treat a major dependency upgrade: run the full eval suite, compare pass rates, deploy to a canary environment first.

What is the right confidence threshold for routing to human review?

There is no universal answer, it depends on your domain and the cost of a wrong automated decision. Start by logging confidence scores for two weeks without acting on them. Plot the distribution. Look at cases where the model was wrong: what was the confidence score? That empirical data will tell you where to set the threshold. A common starting point is 0.85 for high-stakes pipelines and 0.70 for lower-stakes ones, but measure before you commit.

Do I need a separate eval framework or can I use unit tests?

Both, for different purposes. Unit tests cover deterministic logic: your retry code, your schema validator, your fallback routing. Evals cover model behavior: does the pipeline produce correct outputs on representative inputs? Use a dedicated eval framework (Braintrust, LangSmith, or a simple homegrown harness) for model behavior. Unit tests will not catch prompt drift and evals will not replace unit test coverage of your application logic.

Ready to Build a Reliable AI System?

The patterns in this article, structured output contracts, schema validation, retry and fallback chains, idempotency, tool-call guardrails, evals, observability, and human-in-the-loop gates, are the standard architecture for production LLM systems. None of them are complicated individually. The hard part is knowing which ones to prioritize for your specific context, how to sequence them, and where the traps are in your particular stack.

If you are building or scaling an AI system and want an experienced pair of eyes on the architecture before you hit production problems, I offer focused AI architecture advisory engagements. I work directly with your engineering team, not through account managers or templated deliverables. You can learn more about my background at my about page or see past work at my projects page. When you are ready to talk, reach out directly.

Work with me on your AI architecture

Thanks for reading! I hope this was useful. If you have questions or thoughts, feel free to reach out.

Content Creation Process: This article was generated via a semi-automated workflow using AI tools. I prepared the strategic framework, including specific prompts and data sources. From there, the automation system conducted the research, analysis, and writing. The content passed through automated verification steps before being finalized and published without manual intervention.

Mahmoud Zalt

About the Author

I’m Zalt, a technologist with 16+ years of experience, passionate about designing and building AI systems that move us closer to a world where machines handle everything and humans reclaim wonder.

Let's connect if you're working on interesting AI projects, looking for technical advice or want to discuss anything.

Support this content

Share this article

Get notified of the next one

I'll email you when I publish something new. No spam, leave anytime.

CONSULTING

AI advisory. From strategy to production.

Architecture, implementation, team guidance.