Django middleware that captures errors and exceptions, sends them to OpenAI for a detailed explanation, and prints the explanation to stdout when debug mode is enabled. Supports both sync and async views.
Project description
Django Explain Errors Middleware
This Django middleware captures errors and exceptions, sends them to OpenAI for explanation, and prints the explanation to stdout when debug mode is enabled. It can optionally ground explanations in your own project source code using a local vector index (RAG), so explanations reference the actual code that failed instead of staying generic.
The middleware supports both synchronous (WSGI) and asynchronous (ASGI) views. It auto-detects the view chain at startup and routes requests through the matching sync or async path, so no extra configuration is required to use it under either server type. Tracebacks are sanitized before leaving the process, and API calls are rate limited. It uses an environment variable to securely manage the OpenAI API key.
Features
- Captures Django errors and exceptions
- Uses OpenAI to explain the error
- Optional codebase-aware explanations (RAG) backed by a local sqlite-vec index (see the RAG section below)
- Redacts secrets, tokens, and emails from tracebacks before sending
- Rate limits API calls with a configurable sliding window
- Works with both sync (WSGI) and async (ASGI) views
- Securely manages the OpenAI API key using environment variables
Installation
- Install django-explain-errors by running:
pip install django-explain-errors
-
Add the middleware to your Django project:
-
Open your
settings.pyfile and add the middleware to theMIDDLEWARElist. Ensure that the middleware is added last in the list:MIDDLEWARE = [ ... 'explain_errors.middleware.ExplainErrorsMiddleware', ]
-
-
Set up environment variables:
-
Create a
.envfile in your project's root directory and add your OpenAI API key. Alternatively, you can set the API key insettings.py:OPENAI_API_KEY=your_openai_api_key_here
-
Usage
-
Ensure DEBUG is set to True:
Open your
settings.pyfile and set:DEBUG = True
-
Trigger an error in your Django application:
The middleware will capture the error, send it to OpenAI for explanation, and print the explanation to stdout. When an exception is caught, it returns a JSON
500response containing the error message and the explanation.
Async Support
The middleware exposes both sync_capable = True and async_capable = True. At initialization it inspects get_response to decide whether it is part of a sync or async chain:
- Under WSGI (for example
runserverwith sync views), requests flow through the synchronous handler. - Under ASGI (for example with async views), requests are awaited through the async handler. The blocking OpenAI call is offloaded with
asgiref.sync.sync_to_asyncso the event loop is not blocked.
No additional settings are needed. Place the middleware last in MIDDLEWARE for both modes.
Configuration
| Setting / variable | Required | Description |
|---|---|---|
OPENAI_API_KEY (env or settings) |
Yes, when DEBUG=True |
API key used to authenticate with OpenAI. Read first from the environment, then from settings. |
DEBUG |
Yes | The middleware is only active when DEBUG=True. When False, requests pass through untouched. |
OPENAI_MODEL |
No | Model used for explanations. Defaults to gpt-4o-mini. |
OPENAI_MAX_TOKENS |
No | Maximum tokens in the explanation. Defaults to 150. |
OPENAI_TIMEOUT |
No | Request timeout in seconds for the OpenAI client. Defaults to 10. |
OPENAI_MAX_TRACEBACK_CHARS |
No | Traceback is trimmed to its last N characters before being sent. Defaults to 3000. |
Codebase-aware explanations (RAG)
By default, explanations are generated from the traceback alone. With the optional RAG (retrieval-augmented generation) layer enabled, the middleware also retrieves the most relevant chunks of your own project's source code from a local vector index and includes them in the prompt, so explanations can reference your actual functions and classes instead of guessing at them.
This feature is opt-in and adds no dependencies or behavior unless enabled.
Install the extra
pip install django-explain-errors[rag]
This pulls in sqlite-vec, a single-file, no-server vector store. The core package stays dependency-light if you don't need RAG.
Build the index
Add explain_errors to INSTALLED_APPS (needed for Django to discover the
management command), then run:
python manage.py build_error_index
This walks your project, chunks Python files by top-level function/class (and other text files by fixed-size line windows), embeds each chunk with the OpenAI embeddings API, and writes them to a local index file. Re-run it whenever your source changes meaningfully — indexing is not automatic. Rebuilding is idempotent: it builds into a temp file and atomically replaces the previous index.
Enable it
# settings.py
EXPLAIN_ERRORS_RAG_ENABLED = True
Settings
| Setting | Default | Description |
|---|---|---|
EXPLAIN_ERRORS_RAG_ENABLED |
False |
Master switch for the RAG layer. |
EXPLAIN_ERRORS_RAG_INDEX_PATH |
<BASE_DIR>/.explain_errors_index.db |
Path to the local vector index file. |
EXPLAIN_ERRORS_RAG_TOP_K |
4 |
Number of chunks retrieved and injected into the prompt. |
EXPLAIN_ERRORS_RAG_EMBED_MODEL |
"text-embedding-3-small" |
OpenAI embedding model used for indexing and retrieval. |
EXPLAIN_ERRORS_RAG_INCLUDE |
None (defaults to BASE_DIR) |
List of directories to index. |
EXPLAIN_ERRORS_RAG_EXCLUDE |
migrations, venvs, node_modules, static, media, .git |
Directory names to skip while indexing. |
EXPLAIN_ERRORS_RAG_MAX_PROMPT_CHARS |
6000 |
Combined character budget for the traceback + retrieved source sections of the prompt. |
Every chunk of source code and every retrieval query is passed through the
same sanitize_traceback() redaction used for tracebacks, so secrets in
your source files are never sent to OpenAI or written to the index.
If RAG is enabled but the index is missing, sqlite-vec isn't installed, or
retrieval fails for any reason, the middleware logs a warning and falls back
to the traceback-only prompt — it never breaks error reporting.
RAG-grounded explanations tend to be longer than traceback-only ones. Consider raising OPENAI_MAX_TOKENS (for example to 500) when RAG is enabled so explanations are not truncated.
.gitignore
The index file is a local build artifact, not something to commit. Add it
to your project's .gitignore:
.explain_errors_index.db
(Adjust the path if you set EXPLAIN_ERRORS_RAG_INDEX_PATH to something
else.)
Before / after
Without RAG — traceback only:
Your
ValueErroris raised because the value passed tofoo()couldn't be converted to an integer. Check wherefoo()is called and make sure you're passing a numeric string.
With RAG — grounded in the actual function:
In
myapp/utils.py,foo()callsint(value)on line 12 without atry/except, so any non-numericvalueraisesValueErrorstraight through to the caller. Sincefoo()is called frommyapp/views.pywith unvalidated form input, add validation there or wrap theint()call infoo()with a clear error message.
Example
Here is an example of how to use the middleware in a Django project:
# settings.py
DEBUG = True
MIDDLEWARE = [
...
'explain_errors.middleware.ExplainErrorsMiddleware',
]
# .env
OPENAI_API_KEY=your_openai_api_key_here
When an error occurs, you will see an explanation printed to stdout.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Contributing
Contributions are welcome! Please open an issue or submit a pull request for any improvements or bug fixes.
Acknowledgements
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_explain_errors-0.3.0.tar.gz.
File metadata
- Download URL: django_explain_errors-0.3.0.tar.gz
- Upload date:
- Size: 18.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0a01a2805a395ce86c82757fc674c31a40ae9d57b82f162c7e0acc3c8b4c7f3
|
|
| MD5 |
3682acd5ab752a949f8ff8478a9bf757
|
|
| BLAKE2b-256 |
a9dad5c43331bee18bd2897010a87648e74c080fbc374d9decb10ee71f056432
|
Provenance
The following attestation bundles were made for django_explain_errors-0.3.0.tar.gz:
Publisher:
publish.yml on topunix/django-explain-errors
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_explain_errors-0.3.0.tar.gz -
Subject digest:
f0a01a2805a395ce86c82757fc674c31a40ae9d57b82f162c7e0acc3c8b4c7f3 - Sigstore transparency entry: 2194857786
- Sigstore integration time:
-
Permalink:
topunix/django-explain-errors@1888d5cf9871f963e5b501a8fc4803800562cb94 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/topunix
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1888d5cf9871f963e5b501a8fc4803800562cb94 -
Trigger Event:
release
-
Statement type:
File details
Details for the file django_explain_errors-0.3.0-py3-none-any.whl.
File metadata
- Download URL: django_explain_errors-0.3.0-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f07baefe8b0a059f999453c9ecfa22b8767f68952c30ca1a9b730e89a79a896
|
|
| MD5 |
5afc6579443d505e6578123c49f624e8
|
|
| BLAKE2b-256 |
3c80e7483c148be43814a5382cf885423ffc9c1e88d785b73bc90bb880e5e104
|
Provenance
The following attestation bundles were made for django_explain_errors-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on topunix/django-explain-errors
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_explain_errors-0.3.0-py3-none-any.whl -
Subject digest:
4f07baefe8b0a059f999453c9ecfa22b8767f68952c30ca1a9b730e89a79a896 - Sigstore transparency entry: 2194857790
- Sigstore integration time:
-
Permalink:
topunix/django-explain-errors@1888d5cf9871f963e5b501a8fc4803800562cb94 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/topunix
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1888d5cf9871f963e5b501a8fc4803800562cb94 -
Trigger Event:
release
-
Statement type: