Errors and exceptions
Read tracebacks, catch what you can handle, and fail loudly on everything else.
Why this matters in AI / ML / GenAI
LLM APIs time out, rate-limit, and return malformed JSON. GPUs run out of memory. Files go missing mid-pipeline. Retrying the right exceptions — and not swallowing the rest — is the difference between a resilient service and a silent data-corruption incident.
Reading a traceback
Read tracebacks bottom-up. The last line is the exception type and message; the lines above are the call stack, most recent last.
Types you will meet constantly:
KeyError— dict key missing (bad API response shape)TypeError— wrong type (a string where a float belongs)ValueError— right type, bad value (float("abc"))IndexError— list index out of rangeFileNotFoundError— path wrong or file not mountedZeroDivisionError— empty batch used as a denominator
try / except / else / finally
Catch specific exceptions. except Exception: swallows bugs; a bare except: also catches Ctrl+C and should never appear in your code.
else runs when no exception occurred. finally always runs — use it to release resources.
raise re-raises the current exception after logging. raise ValueError("msg") from err preserves the original cause, which keeps the traceback useful.
Define your own exception types for domain errors: class RetrievalError(Exception): pass. Callers can then handle your failure mode without guessing at strings.
Retry with backoff
Transient failures (429 rate limits, 503, timeouts) deserve a retry. Permanent ones (401 auth, 400 bad request) do not — retrying them just burns quota.
Standard shape: try, catch the transient type, sleep for an increasing delay (2 ** attempt), try again up to N times, then give up and raise. Production code adds jitter so a fleet of workers does not retry in lockstep.
Copy-paste examples
Copy into your own editor, or load one into the compiler below and press Run.
Catch specific exceptions
Each block handles one failure mode with a useful message.
payload = {"model": "gpt-4.1-mini"}
try:
temperature = float(payload["temperature"])
except KeyError:
temperature = 0.2
print("temperature missing, using default")
except (TypeError, ValueError):
temperature = 0.2
print("temperature unparseable, using default")
else:
print("parsed temperature")
finally:
print("temperature =", temperature)Custom exception + raise from
Domain errors let callers handle your failure without string matching.
class RetrievalError(Exception):
pass
def retrieve(query, index):
try:
return index[query]
except KeyError as err:
raise RetrievalError(f"no documents for: {query}") from err
index = {"python": ["doc-1", "doc-2"]}
print(retrieve("python", index))
try:
retrieve("rust", index)
except RetrievalError as err:
print("handled:", err)
print("caused by:", type(err.__cause__).__name__)Retry with exponential backoff
Simulated flaky API. Real code sleeps with time.sleep and adds jitter.
attempts = {"count": 0}
def flaky_call():
attempts["count"] += 1
if attempts["count"] < 3:
raise TimeoutError("upstream timeout")
return {"answer": "ok"}
def call_with_retry(fn, max_attempts=5):
for attempt in range(max_attempts):
try:
return fn()
except TimeoutError as err:
wait = 2 ** attempt
print(f"attempt {attempt + 1} failed ({err}); retry in {wait}s")
raise RuntimeError("all retries exhausted")
print(call_with_retry(flaky_call))Validate an LLM config safely
Try it — in-browser Python
Set temperature to "hot" or delete the key and see which branch runs.
Output
Python runs in your browser. First run downloads the runtime.
Press Run (or Ctrl+Enter) to execute.
CPython in WebAssembly. Stdlib works. NumPy and pandas load on demand. No input(), no GPU, no network installs.
Takeaways
- Read tracebacks bottom-up; the last line names the real problem.
- Catch specific exceptions — never use a bare except.
- Retry transient failures with backoff; fail fast on auth and validation errors.