The problem: AI applications depend on files, networks, APIs, and model responses. A file may be missing, a network may time out, or returned JSON may have the wrong shape even when your Python syntax is correct.
What you will learn: You will separate syntax mistakes from runtime exceptions and recover only where recovery makes sense. The goal is not to hide failure; it is to explain it, preserve useful evidence, clean up resources, and avoid unsafe retries that could make the situation worse.
1. What Are Errors?
The goal is not to pretend failures never happen. The goal is to make each failure visible, understandable, and safely handled.
risky operation
├── succeeds → continue normally
└── fails → Python raises an exception
↓
matching handler?
├── yes → recover or explain
└── no → stop and show traceback
A traceback is the path of function calls that led to the failure. Read its final line for the exception type and message, then use the earlier lines to find how execution reached the problem.
Two Times an Error Can Appear
An error is something going wrong while Python tries to run your code. Python has two broad categories:
| Type | What it means | Example |
|---|---|---|
| Syntax Error | Code is not valid Python at all — it can’t even start running | Missing colon, mismatched brackets |
| Runtime Error (Exception) | Code is valid, but something goes wrong while running | Dividing by zero, missing dictionary key, failed API call |
Grammar Failure Versus Action Failure
A syntax error is like a grammatically broken sentence — it can’t even be read aloud. A runtime error is like a grammatically correct sentence that fails when acted on — “open the door” is valid English, but it fails if there’s no door.
2. Syntax Errors
if True
print("missing colon")
SyntaxError: expected ':'
Python refuses to run the program at all until this is fixed — it’s caught before execution even begins.
3. Runtime Errors and Exceptions
tokens_used = 100
max_tokens = 0
print(tokens_used / max_tokens)
ZeroDivisionError: division by zero
This code is syntactically valid Python — the error only appears once Python actually tries to execute that line.
🤖 How Is This Used in AI? This distinction matters because runtime errors are exactly what happens when you call an unreliable external service: the code is fine, but the conditions at that moment (no internet, invalid API key, model timeout) cause a failure.
4. try / except
What Is It?
A way to attempt code that might fail, and handle the failure gracefully instead of crashing the whole program.
Why Does It Exist?
You cannot control the outside world. APIs go down. Files go missing.
Networks time out. try/except lets your program survive these
situations instead of dying immediately.
🧠 Intuition
try is “attempt this, and if something goes wrong, don’t panic — go
straight to the plan B I’ve written.”
Real-World Analogy
Think of it like a fire drill: “Try to keep working normally. If the alarm goes off (an exception), stop, follow the evacuation plan (the except block), instead of standing there confused.”
Syntax
try:
# code that might fail
except SomeErrorType:
# what to do if it fails
Example
def call_ai_api(prompt):
if prompt == "":
raise ValueError("Prompt cannot be empty")
return f"Response to: {prompt}"
try:
result = call_ai_api("")
print(result)
except ValueError as e:
print(f"API call failed: {e}")
print("Program keeps running after the error.")
Expected Output:
API call failed: Prompt cannot be empty
Program keeps running after the error.
How It Works
- Python runs the
tryblock. - If a
ValueErroroccurs, execution jumps straight toexcept ValueErrorinstead of crashing the program. as ecaptures the actual error object so you can inspect or log its message.- Code after the whole
try/exceptcontinues running normally.
💡 Exception Propagation (Bubbling)
If an exception is raised inside a function, and that function doesn’t have a try/except block to catch it, the exception doesn’t just disappear. It bubbles up (propagates) to the code that called the function. If that caller doesn’t catch it, it bubbles up to the caller’s caller, and so on. If it reaches the very top level of your program without being caught, only then does the program crash.
graph TD
subgraph ExceptionPropagation ["Exception Propagation (Bubbling)"]
Level3["[ Low-level Tool: call_ai_api() ]<br/>Error raised: ValueError"] -->|bubbles up to| Level2["[ Coordinator: run_agent() ]<br/>No try/except block here"]
Level2 -->|bubbles up to| Level1["[ Main Script: main() ]<br/>try:<br/> run_agent()<br/>except ValueError:<br/> print('Handled!')"]
end
🤖 How Is This Used in AI?
import time
def call_llm_with_retry(prompt, max_retries=3):
for attempt in range(max_retries):
try:
# pretend this sometimes raises a timeout
if attempt < 2:
raise TimeoutError("Model took too long to respond")
return f"Response to: {prompt}"
except TimeoutError as e:
print(f"Attempt {attempt + 1} failed: {e}")
time.sleep(1)
return None
result = call_llm_with_retry("Explain embeddings")
print(result)
Expected Output:
Attempt 1 failed: Model took too long to respond
Attempt 2 failed: Model took too long to respond
Response to: Explain embeddings
This retry loop is a real, common pattern wrapped around actual LLM API calls in production code.
⚠️ Common Beginner Mistake — catching everything blindly:
try: result = call_ai_api(prompt) except: pass # silently swallows EVERY error, including bugs in your own codeA bare
except:hides real bugs along with expected failures. Always catch the specific exception type you expect, so genuine programming mistakes still surface loudly instead of vanishing silently.
5. else
What Is It?
Code that runs only if the try block succeeded with no exception.
try:
response = "valid response"
except ValueError:
print("failed")
else:
print("Success:", response)
Expected Output:
Success: valid response
🤖 How Is This Used in AI? Use else for logic that should only run
after a successful API call — e.g., only cache a response, or only log
“success,” if nothing went wrong.
6. finally
What Is It?
Code that always runs, whether or not an exception occurred — used for cleanup.
try:
print("calling API...")
raise ConnectionError("network unreachable")
except ConnectionError as e:
print(f"Error: {e}")
finally:
print("Closing connection (always runs).")
Expected Output:
calling API...
Error: network unreachable
Closing connection (always runs).
🤖 How Is This Used in AI? Closing a network connection, releasing a
file handle, or logging “request finished” — regardless of whether the
call succeeded — belongs in finally.
7. Raising Exceptions
What Is It?
Deliberately triggering an error yourself with raise, when your own code
detects something is wrong.
def call_model(prompt, max_tokens):
if max_tokens <= 0:
raise ValueError("max_tokens must be greater than 0")
return f"Response (limited to {max_tokens} tokens): {prompt}"
try:
call_model("hello", max_tokens=-10)
except ValueError as e:
print(f"Invalid input: {e}")
Expected Output:
Invalid input: max_tokens must be greater than 0
🧠 Intuition: raise is how a function says “I refuse to continue with
this input — something is clearly wrong, and I want the caller to know
immediately” rather than silently producing a broken result.
🤖 How Is This Used in AI? Validating inputs before they reach an expensive API call — e.g., rejecting an empty prompt or a negative token limit before you ever spend money calling the model.
💡 Contextual Exceptions
When you raise an exception, always pass a descriptive message containing the contextual details of what failed. In production systems, debug logs often only capture the exception message itself. If your code raises a generic message like raise ValueError("Invalid model"), you won’t know which model name was passed or what the allowed models are.
Instead, write:
VALID_MODELS = {"gpt-4o-mini", "claude-3-5-sonnet"}
def validate_model(model_name: str) -> None:
if model_name not in VALID_MODELS:
raise ValueError(
f"Invalid model name '{model_name}'. Expected one of {VALID_MODELS}."
)
This single habit saves hours of production troubleshooting time.
8. Custom Exceptions
What Is It?
Your own exception types, created by subclassing Python’s built-in
Exception — useful when generic error types (ValueError,
TimeoutError) don’t clearly describe what actually went wrong in your
application.
class ModelResponseError(Exception):
"""Raised when the AI model returns an invalid or empty response."""
pass
class RateLimitError(Exception):
"""Raised when the API rate limit has been exceeded."""
pass
def process_response(response_text):
if not response_text:
raise ModelResponseError("Model returned an empty response")
return response_text.upper()
try:
process_response("")
except ModelResponseError as e:
print(f"Handling model error: {e}")
except RateLimitError as e:
print(f"Handling rate limit: {e}")
Expected Output:
Handling model error: Model returned an empty response
💡 Exception Chaining (raise ... from)
In production AI applications, you will often catch a low-level error (like a network timeout from requests or an SDK-specific error) and wrap it inside a custom exception that your application’s coordinator knows how to handle.
To ensure you don’t lose the original error’s traceback (which is vital for debugging), you should use exception chaining with raise ... from:
import requests
class LLMConnectionError(Exception):
"""Raised when the LLM service is unreachable."""
pass
def call_raw_api(prompt: str) -> dict:
try:
response = requests.post("https://api.example.com/v1/generate", json={"prompt": prompt})
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
# Chain the custom exception to the original network error
raise LLMConnectionError("Failed to reach the AI model service") from e
If this error is raised, the traceback will print both the original RequestException AND your custom LLMConnectionError, preserving the full story of what failed.
🤖 How Is This Used in AI? Real AI codebases define exceptions like
ModelResponseError, RateLimitError, InvalidPromptError, or
ContextLengthExceededError — this makes error handling specific and
readable: you immediately know from the exception name what category of
AI-related failure occurred, and can react differently to each.
✅ Key Takeaway: Custom exceptions turn “something broke” into “this specific thing broke,” which is essential once your AI app has many different ways of failing.
9. Exception Handling in AI Applications
Realistic, combined example — a resilient LLM call wrapper:
import time
class ModelTimeoutError(Exception):
pass
class InvalidPromptError(Exception):
pass
def call_llm(prompt, max_retries=3):
if not prompt.strip():
raise InvalidPromptError("Prompt cannot be empty or whitespace")
for attempt in range(1, max_retries + 1):
try:
print(f"Attempt {attempt}: calling model...")
if attempt < 3:
raise ModelTimeoutError("Simulated timeout")
return f"Model response to: {prompt}"
except ModelTimeoutError as e:
print(f" Timeout: {e}. Retrying...")
time.sleep(0.5)
finally:
print(f" Attempt {attempt} finished.")
raise ModelTimeoutError("Model call failed after all retries")
try:
answer = call_llm("Explain vector databases")
print("Final answer:", answer)
except InvalidPromptError as e:
print("Bad input, not retrying:", e)
except ModelTimeoutError as e:
print("Gave up:", e)
Expected Output:
Attempt 1: calling model...
Timeout: Simulated timeout. Retrying...
Attempt 1 finished.
Attempt 2: calling model...
Timeout: Simulated timeout. Retrying...
Attempt 2 finished.
Attempt 3: calling model...
Attempt 3 finished.
Final answer: Model response to: Explain vector databases
This single function demonstrates the realistic shape of production AI error handling: validate input early, retry transient failures, clean up after every attempt, and let genuinely unrecoverable errors bubble up to be handled by the caller.
Decide What Kind of Failure Happened
Not every error should receive the same response:
| Failure | Example | Sensible response |
|---|---|---|
| Invalid input | Missing question | Explain what the user must correct |
| Temporary failure | Service briefly unavailable | Retry a limited number of times |
| Permission failure | Wrong API key | Stop and fix access; repeated retries will not help |
| Programming bug | Misspelled variable | Record the traceback and fix the code |
Catch an exception only where the program can add context, recover, or present a
clear message. Avoid except Exception: pass; it hides the evidence needed to
find a defect. Retry only operations that are safe to repeat, because repeating
a payment or a state-changing tool call can perform the action twice.
Use finally or, preferably, a context manager such as with open(...) when a
resource must close even after failure.
Module Summary
You can now distinguish syntax errors from runtime exceptions, handle
failures gracefully with try/except/else/finally, deliberately signal
problems with raise, and design your own exception types so failures in
your AI code are specific and meaningful instead of generic and confusing.
AI Connection
AI applications constantly depend on things outside their control — APIs, networks, models that sometimes misbehave. Exception handling is what separates a fragile script that crashes the moment an API hiccups from a production-grade AI service that retries, logs, and degrades gracefully instead of falling over.
Mini Practice
- Write a function that raises a
ValueErroriftemperatureis outside the range0.0–2.0, and call it inside atry/exceptthat prints a friendly error message. - Write a retry loop (using
whileorfor) around a function that simulates failing twice before succeeding, printing each attempt. - Define a custom exception
EmptyDocumentErrorand raise it from a function that processes a document, if the document is an empty string. - Write a
try/except/finallyblock where thefinallyclause prints"Request logged"no matter what happens in thetry. - Explain, in your own words, why a bare
except:is dangerous in an AI application that calls external APIs.
Next: Module 7 — Files and Data — reading/writing text, CSV, and JSON, the data layer underneath every AI pipeline.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed