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.

11 min Free

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.

Ready
# 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 ← →.

00/08

Variables

Nothing defined yet.

Output

Nothing printed yet.

What to notice

  1. raise stops the function immediately. After raise ValueError(...) on line 8, lines 9 and 10 of pay never run, so a rejected payment cannot change the balance. Exceptions are how a function says “I cannot do this” without returning a fake value.
  2. except matches by type, including subclasses. except ValueError catches ValueError and anything derived from it, but not KeyError. Catch the specific errors you can actually handle, and let the rest pass through.
  3. else keeps success code out of the try. If “Paid:” were printed inside the try, a typo there would be caught by the same except and reported as a declined payment. else runs only when the try block finished cleanly.
  4. finally always 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.)
  5. 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.
  6. except Exception catches almost everything, and that is the problem. It also hides bugs you did not expect, like the KeyError here. If you must use it, log and re-raise with a bare raise. Never catch BaseException, which would also swallow Ctrl-C and sys.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.

Check your understanding

1. checkout has except ValueError. pay raises KeyError. Which blocks run?
2. Why put the success message in else rather than at the end of the try block?
3. In an ADK tool, why avoid wrapping everything in except Exception: return {"status": "error"}?