When building workflows that rely on large language models, I quickly learned that non-deterministic outputs are a constant reality. You cannot treat an AI response like a static API payload because the model might hallucinate or change its formatting without warning. My approach to defensive programming focuses on treating every model output as untrusted user input. This mindset shift is critical when you move beyond simple prototypes and into production environments where reliability matters. If you are still setting up your initial architecture, check out our main guide on how to convert any real-world task into an AI workflow to ensure your pipeline handles data ingestion correctly.
The first line of defense involves strict input and output schema validation. I always enforce a rigid structure using libraries like Pydantic to ensure the model returns exactly what the downstream code expects. If the model returns a string when a JSON object is required, my code catches the exception immediately. I then trigger a secondary call or a fallback mechanism to prevent the entire system from crashing. This pattern prevents malformed data from propagating through your application logic and causing downstream failures that are much harder to debug later.
Retry mechanisms are the second pillar of my defensive strategy. Network instability and model timeouts occur frequently in distributed systems. I implement exponential backoff strategies to avoid overwhelming the API provider during periods of high latency. My standard configuration includes a limit of three attempts before logging a failure and alerting the engineering team. This simple logic significantly improves the success rate of automated tasks that would otherwise fail due to temporary service degradation or transient model errors.
Beyond structural checks, I incorporate semantic validation to verify the quality of the generated content. I often use a smaller, faster model to act as a judge for the primary output. This secondary check evaluates the response against specific constraints, such as tone, length, or factual consistency. These are the specific patterns I use for semantic verification:
- Self-correction loops where the model reviews its own output for errors.
- Confidence score filtering to discard responses that fall below a set probability threshold.
- Schema-based validation to ensure JSON keys match the predefined interface exactly.
- Content filtering to strip out prohibited tokens or unwanted conversational filler.
Finally, you must design your application to fail gracefully when all automated attempts are exhausted. I prefer to queue failed tasks into a manual review bucket rather than letting them drop into a black hole. This human-in-the-loop component ensures that high-value operations are never lost due to model quirks. By combining structural validation, smart retry logic, and semantic oversight, you build a system that remains stable regardless of the model’s inherent unpredictability. Defensive programming is not just a coding choice, it is a requirement for anyone building serious software with modern AI agents.







