A traceback is the block of red-looking text Python prints when your script crashes. It looks like punishment, but it is closer to a receipt: it records exactly what broke, where, and which calls led there. By the end of this guide you will be able to take any traceback from an AI API script, name the real problem in under a minute, and point at the single line you need to change. Budget five minutes for the reading method and another ten to wire up the error handler in Step 5.
This guide belongs to Debugging Python AI Errors, the section that covers the specific failures you hit when a Python script talks to a hosted model. The other guides there fix named errors; this one teaches you to identify which error you are looking at in the first place.
Prerequisites
You need Python 3.10 or newer. Python 3.11 and later add the ^^^^ caret markers under the exact expression that failed, which makes tracebacks considerably easier to read, so upgrade if you can. Check what you have:
python --version
Work inside a virtual environment, an isolated folder of packages that belongs to one project. If that phrase is new, read Create a Python Virtual Environment for AI first, then come back. Install the two packages used in the examples:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "openai>=1.40" "python-dotenv>=1.0"
Put your API key in a .env file beside your script:
OPENAI_API_KEY=sk-your-real-key-here
Then add that file to .gitignore before you write another line, so the key never reaches a public repository:
echo ".env" >> .gitignore
Every code block below assumes those four things: Python 3.10+, an active virtual environment, the openai package, and a .env holding your key.
Step 1 — Read it from the bottom up
Python prints a traceback in chronological order: the first call your program made appears at the top, and the failure appears at the bottom. That is the opposite of the order you need. The useful information is at the end, so train yourself to jump straight there.
The last line of any traceback has two halves separated by a colon: the exception type on the left and the message on the right. The type is a Python class name such as AuthenticationError, KeyError or TypeError, and it tells you the category of failure. The message is written by whoever raised it and usually names the specific value that caused trouble. Read those two things, and only then start scrolling up.
Here is a complete traceback from a script that calls OpenAI with a key that the API rejected. Your file paths and the library line numbers will differ, but the shape is always this:
Traceback (most recent call last):
File "/home/you/summariser/app.py", line 24, in <module>
print(summarise(open("notes.txt").read()))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/you/summariser/app.py", line 17, in summarise
response = client.chat.completions.create(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/you/summariser/.venv/lib/python3.12/site-packages/openai/_utils/_utils.py", line 275, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/you/summariser/.venv/lib/python3.12/site-packages/openai/resources/chat/completions/completions.py", line 914, in create
return self._post(
^^^^^^^^^^^
File "/home/you/summariser/.venv/lib/python3.12/site-packages/openai/_base_client.py", line 1266, in post
return cast(ResponseT, self.request(cast_to, opts, stream=stream))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/you/summariser/.venv/lib/python3.12/site-packages/openai/_base_client.py", line 1054, in request
raise self._make_status_error_from_response(err.response) from None
openai.AuthenticationError: Error code: 401 - Incorrect API key provided
Walk it the way the diagram says:
- Last line.
openai.AuthenticationError: Error code: 401 - Incorrect API key provided. Type and message. The key you sent was refused. Nothing about your prompt, your model name or your internet connection is implicated. - Deepest frame naming your file. The second block,
app.py, line 17, in summarise. That is where your program touched the API. The fix lives in that function or in the value it was given. - The library frames. Five frames inside
site-packages/openai/. They describe how the SDK turned your call into an HTTP request and how it converted the server's 401 response into a Python exception. You did not write them and you will not edit them. - The header.
Traceback (most recent call last)is a fixed sentence Python prints every time. It carries no information about your specific failure.
Four lines read, problem identified: the key. The fix is covered in Fix the 401 Unauthorized Error in OpenAI Python.
Step 2 — Find the deepest frame that is yours
Each indented File "..." block is a frame: one function call that was still in progress when the crash happened. Python stacks them as it goes. Your script calls a function, that function calls the SDK, the SDK calls its HTTP layer, and that layer raises. Every one of those calls gets a frame.
That is why the very last frame is nearly always library code. It is the place the exception was raised, not the place the mistake was made. Someone else's carefully written code noticed that the thing you handed it was wrong and complained. So the frame you care about is the deepest one whose path is inside your project folder rather than inside site-packages/.
A quick way to spot the boundary: look for site-packages or lib/python3. in the path. Anything containing those strings is installed code. Anything else is yours.
There is one honest exception to the rule. If the traceback contains no frame from your project at all, the failure happened somewhere your code was invoked indirectly — inside a background thread, a callback, or a framework that called you. In that case read the exception type and the library name, and search for that combination rather than hunting for your own line.
Step 3 — Follow the chain when there are two tracebacks
Sometimes Python prints two tracebacks separated by a sentence. The two sentences you will meet are:
During handling of the above exception, another exception occurred:— a second error happened while anexceptblock was already running.The above exception was the direct cause of the following exception:— you wroteraise NewError(...) from original, explicitly linking them.
In both cases Python prints the original failure first and the newer one last. This inverts the bottom-up habit you just learned, so slow down here: the bottom traceback is what stopped your program, but the top traceback is usually why.
This script produces a chain. The model is asked for JSON, wraps its answer in a Markdown code fence, json.loads refuses it, and the handler raises a friendlier error of its own:
import json
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv() # .env is listed in .gitignore
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def extract_price(description: str) -> float:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": f"Return JSON with a price key for: {description}"},
],
)
data = json.loads(response.choices[0].message.content)
return float(data["price"])
def process(description: str) -> float:
try:
return extract_price(description)
except Exception:
raise RuntimeError(f"no price found in: {description[:40]}")
if __name__ == "__main__":
print(process("A 2-litre stainless steel kettle, boxed, unused."))
When the model answers with a code fence instead of bare JSON, the output looks like this:
Traceback (most recent call last):
File "/home/you/prices/app.py", line 21, in process
return extract_price(description)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/you/prices/app.py", line 18, in extract_price
data = json.loads(response.choices[0].message.content)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/you/prices/app.py", line 27, in <module>
print(process("A 2-litre stainless steel kettle, boxed, unused."))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/you/prices/app.py", line 23, in process
raise RuntimeError(f"no price found in: {description[:40]}")
RuntimeError: no price found in: A 2-litre stainless steel kettle,
The bottom traceback says RuntimeError: no price found. True but useless — you wrote that message. The top traceback says JSONDecodeError: Expecting value: line 1 column 1 (char 0), which is the real story: the model's reply did not start with a JSON value. Fix JSONDecodeError with AI API Responses in Python covers stripping code fences before parsing.
If you add from exc to your raise statement, Python swaps the joining sentence for the "direct cause" wording and keeps the same two blocks. Either way, read the top block first.
Step 4 — Reproduce it in the smallest script that still fails
Once you know the exception type and the guilty line, shrink the problem. A traceback from a 300-line automation script mixes your bug with everything else the script does. Copy the failing call into a new file with nothing else in it, and run that instead.
Keep only what the failing line needs: the imports, the client, and one hard-coded input. No loops, no file reading, no command-line arguments.
"""repro.py — the smallest script that still reproduces the failure."""
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv() # .env is listed in .gitignore
key = os.getenv("OPENAI_API_KEY")
print("Key loaded:", bool(key), "| starts with:", (key or "")[:7])
client = OpenAI(api_key=key)
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Reply with the single word: ok"}],
)
print(repr(response.choices[0].message.content))
Run it with python repro.py. Two outcomes, both useful. If it fails the same way, you have a ten-line file to experiment on and you can change one thing at a time. If it succeeds, the problem is not the API call at all — it is something your larger script does to the input beforehand, and you now know where to look.
That print of the key is deliberate. Printing bool(key) and the first seven characters confirms load_dotenv() found your file without exposing the secret in your terminal history. A surprising share of AuthenticationError reports turn out to be None arriving where a key should be, usually because the script was run from a different folder than the one holding .env.
Step 5 — Make future failures readable
Reading tracebacks is a skill you should need less often over time. The way to need it less is to catch exceptions where you can do something sensible about them and print a one-line summary instead of a wall of frames.
Two attributes do almost all the work. type(e).__name__ gives the exception's class name as a plain string, and str(e) gives its message. Together they are the same information as the traceback's last line, in a form you can log, count, or branch on.
import os
import traceback
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv() # .env is listed in .gitignore
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def ask(prompt: str, show_frames: bool = False) -> str | None:
"""Return the model's reply, or None after printing a readable error."""
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
)
return response.choices[0].message.content
except Exception as e:
print(f"FAILED [{type(e).__name__}] {e}")
if show_frames:
traceback.print_exc()
return None
if __name__ == "__main__":
for question in ["Name one primary colour.", "Name one prime number."]:
answer = ask(question, show_frames=True)
print(answer if answer is not None else "(skipped)")
Three things changed compared with letting the script crash. The loop finishes instead of stopping on the first bad item. Each failure prints one scannable line such as FAILED [RateLimitError] Error code: 429. And traceback.print_exc() is available behind a flag when you want the full frame list back, so you lose nothing.
Keep the except Exception as e form. A bare except: with no exception class also swallows KeyboardInterrupt, which means Ctrl+C stops working and you can no longer interrupt a stuck loop. When you are ready to record failures to a file rather than the terminal, Log and Monitor AI API Calls in Production shows the pattern.
Quick reference: exception type to next question
| Exception type | What Python is telling you | The question to ask next |
|---|---|---|
ModuleNotFoundError | An import found nothing | Is this the interpreter pip installed into? See Fix ModuleNotFoundError: No Module Named openai |
AuthenticationError | The server refused your key | Did load_dotenv() actually find .env? |
APIConnectionError, SSLError | The request never reached the API | Can this machine reach the internet, and is a proxy in the way? |
AttributeError, IndexError | You read a field that was not there | What does print(response) show the reply actually contains? |
Two of these have dedicated fixes here: Fix Connection and Timeout Errors with AI APIs and Fix SSL: CERTIFICATE_VERIFY_FAILED in Python.
Troubleshooting
SyntaxError: '(' was never closed points at a line that looks perfect. Python reports where it gave up, not where you slipped. An unclosed bracket, quote or brace on the line above swallows the next line into the same expression. Check the line immediately before the one named.
IndentationError: unexpected indent after pasting code. Mixed tabs and spaces, or a stray leading space, on the reported line. Select the block in your editor and re-indent it with spaces only; Choose a Code Editor for Python AI Work covers editors that show whitespace.
The traceback lists only library files and none of yours. Your code was called from inside a framework or a thread, so no frame of yours is on the stack. Search the exception type together with the library name, and add a try/except around the call that hands work to that library.
Nothing prints at all, and the script exits silently. Something is catching the exception and discarding it — usually a bare except: or an except Exception: pass. Replace pass with traceback.print_exc() and run again.
RecursionError: maximum recursion depth exceeded fills the screen. A function is calling itself without a stopping condition, so the same two or three frames repeat thousands of times. Scroll to the very bottom, note the repeating pair of function names, and add the exit condition.
When to use this vs. alternatives
- Reading the traceback vs. a step debugger. Reading is faster for anything with a clear exception type and message, which covers nearly every API failure. Reach for
pdbor your editor's debugger when the script produces a wrong answer rather than an exception, since there is no traceback to read. - Reading the traceback vs. pasting it into a chatbot. Pasting works, but only after you have redacted keys and file paths, and it teaches you nothing about your own project. Read the last line yourself first; ask for help on the part you genuinely cannot decode.
- Printing errors vs. logging them.
printis right while you are developing at the terminal. Once a script runs unattended on a schedule, switch to Python'sloggingmodule so failures get timestamps and land in a file you can review later.
Tracebacks stop being intimidating the moment you accept that only two of their lines are usually about you: the exception line at the bottom and the deepest frame carrying your own filename. Read those two, shrink the failure into a small script, and add a handler that prints type(e).__name__ so the next crash arrives already summarised. Back to Debugging Python AI Errors.
Related guides
- Fix ModuleNotFoundError: No Module Named openai — the first traceback almost everyone meets, and why the interpreter matters.
- Fix Connection and Timeout Errors with AI APIs — what to do when the traceback ends in a network exception.
- Fix the 429 Rate-Limit Error in Python — retry logic for the error type your new handler will catch most often.
- Fix the Context-Length-Exceeded Error in Python — the traceback you get when the prompt itself is too long.
- Create a Python Virtual Environment for AI — the setup that makes
site-packagespaths in a traceback predictable.