Exceptions
An exception stops the current code and travels up until something catches it. Trace a UPI payment three times, with a happy path, a caught error and an uncaught crash, and learn why tools should usually let errors escape.
When something goes wrong, Python raises an exception: an object that describes the problem, such as ValueError("insufficient balance") or KeyError: 'ravi@upi'. Raising stops the current function at once and hands the exception to whoever called it. If nothing catches it, the program ends with a traceback showing the path the exception travelled.
A try statement decides what happens. Its four parts read like this: try the risky code; except a named error type, do this instead; else, if nothing went wrong, do this; and finally, do this no matter what. You only need try and one except; else and finally are there when you need them.
Press Run for the happy path, then switch Try it to “Caught error” and “Uncaught error”. The program is the same each time; only the input changes, and each input lights up a different set of blocks.
# Paying by UPI, with errors handled three different ways.
BALANCE = {"asha@upi": 1200}
def pay(sender, amount):
if amount <= 0:
raise ValueError("amount must be positive")
if BALANCE[sender] < amount: # KeyError if sender is unknown
raise ValueError("insufficient balance")
BALANCE[sender] -= amount
return {"status": "success", "left": BALANCE[sender]}
def checkout(sender, amount):
try:
receipt = pay(sender, amount)
except ValueError as err:
print("Declined:", err)
else:
print("Paid:", receipt)
finally:
print("Attempt logged")
checkout("asha@upi", 500) # 1. happy path: try, else, finally
checkout("asha@upi", 900) # 2. caught: except, finally
checkout("ravi@upi", 100) # 3. KeyError is not caught: finally, then crash
print("never printed")Press Play to run the program line by line, or step with ← →.
Variables
Nothing defined yet.
Output
Nothing printed yet.
What to notice
raisestops the function immediately. Afterraise ValueError(...)on line 8, lines 9 and 10 ofpaynever run, so a rejected payment cannot change the balance. Exceptions are how a function says “I cannot do this” without returning a fake value.exceptmatches by type, including subclasses.except ValueErrorcatchesValueErrorand anything derived from it, but notKeyError. Catch the specific errors you can actually handle, and let the rest pass through.elsekeeps success code out of thetry. If “Paid:” were printed inside thetry, a typo there would be caught by the sameexceptand reported as a declined payment.elseruns only when thetryblock finished cleanly.finallyalways runs. Normal exit, caught error, uncaught error: the “Attempt logged” line printed all three times. That is where cleanup goes. (with open(...)is this pattern packaged up; see the files lesson.)- Read tracebacks from the bottom. The last line names the exception and its message. The lines above show the call chain, newest call last, so you can walk from the crash back to the line that started it.
except Exceptioncatches almost everything, and that is the problem. It also hides bugs you did not expect, like theKeyErrorhere. If you must use it, log and re-raise with a bareraise. Never catchBaseException, which would also swallow Ctrl-C andsys.exit().
Why this matters for AI work
ADK 2.0 handles failures for you: it catches exceptions to retry a step, record telemetry and pause for human input. That only works if the exception reaches the framework. The 2.0 guide warns that a broad except Exception: in a tool masks the failure from the framework and disables the automatic retry for that step, and that catching BaseException breaks its ability to pause a workflow for human input. So inside a tool, validate inputs, catch only the errors you can turn into a useful {"status": "error", ...} reply for the model, and let everything else propagate. Retries and timeouts shows what the framework does once the error escapes.