django-explain-errors 0.4.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. django_explain_errors-0.6.0/PKG-INFO +411 -0
  2. django_explain_errors-0.6.0/README.md +367 -0
  3. django_explain_errors-0.6.0/django_explain_errors.egg-info/PKG-INFO +411 -0
  4. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/django_explain_errors.egg-info/SOURCES.txt +4 -1
  5. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/middleware.py +58 -3
  6. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/setup.py +2 -5
  7. django_explain_errors-0.6.0/tests/test_language.py +158 -0
  8. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/tests/test_middleware.py +53 -5
  9. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/tests/test_rag.py +4 -0
  10. django_explain_errors-0.6.0/tests/test_signals.py +55 -0
  11. django_explain_errors-0.6.0/tests/test_truncation.py +95 -0
  12. django_explain_errors-0.4.0/PKG-INFO +0 -267
  13. django_explain_errors-0.4.0/README.md +0 -220
  14. django_explain_errors-0.4.0/django_explain_errors.egg-info/PKG-INFO +0 -267
  15. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/LICENSE +0 -0
  16. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/MANIFEST.in +0 -0
  17. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/django_explain_errors.egg-info/dependency_links.txt +0 -0
  18. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/django_explain_errors.egg-info/requires.txt +0 -0
  19. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/django_explain_errors.egg-info/top_level.txt +0 -0
  20. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/__init__.py +0 -0
  21. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/client.py +0 -0
  22. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/management/__init__.py +0 -0
  23. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/management/commands/__init__.py +0 -0
  24. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/management/commands/build_error_index.py +0 -0
  25. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/rag/__init__.py +0 -0
  26. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/rag/indexer.py +0 -0
  27. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/rag/retriever.py +0 -0
  28. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/rag/store.py +0 -0
  29. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/sanitize.py +0 -0
  30. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/explain_errors/throttle.py +0 -0
  31. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/setup.cfg +0 -0
  32. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/tests/test_build_error_index.py +0 -0
  33. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/tests/test_client.py +0 -0
  34. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/tests/test_sanitize.py +0 -0
  35. {django_explain_errors-0.4.0 → django_explain_errors-0.6.0}/tests/test_throttle.py +0 -0
@@ -0,0 +1,411 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-explain-errors
3
+ Version: 0.6.0
4
+ Summary: Django middleware that explains unhandled exceptions in DEBUG using an LLM, optionally grounded in your own project source via a local vector index. Works with OpenAI, Claude, or any OpenAI-compatible endpoint including local models.
5
+ Home-page: https://github.com/topunix/django-explain-errors
6
+ Author: topunix
7
+ Author-email: topunixguy@gmail.com
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Framework :: Django
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: Django>=4.2
27
+ Requires-Dist: openai<4.0,>=1.0
28
+ Requires-Dist: python-dotenv>=1.0
29
+ Requires-Dist: asgiref>=3.6
30
+ Provides-Extra: rag
31
+ Requires-Dist: sqlite-vec>=0.1.0; extra == "rag"
32
+ Dynamic: author
33
+ Dynamic: author-email
34
+ Dynamic: classifier
35
+ Dynamic: description
36
+ Dynamic: description-content-type
37
+ Dynamic: home-page
38
+ Dynamic: license
39
+ Dynamic: license-file
40
+ Dynamic: provides-extra
41
+ Dynamic: requires-dist
42
+ Dynamic: requires-python
43
+ Dynamic: summary
44
+
45
+ # Django Explain Errors Middleware
46
+
47
+ This Django middleware captures unhandled errors and exceptions, sends them
48
+ to a language model for explanation, and prints the explanation to stdout
49
+ when debug mode is enabled. It works with the OpenAI API out of the box, with
50
+ Anthropic's Claude models through Anthropic's OpenAI-compatible endpoint, and
51
+ with any other OpenAI-compatible endpoint (Ollama, LM Studio, Azure, or a
52
+ corporate gateway) by setting a base URL, so explanations can run entirely on
53
+ a local model if you prefer not to send code off your machine.
54
+
55
+ It can optionally ground explanations in your own project source using a
56
+ local vector index (RAG), so explanations reference the actual code that
57
+ failed instead of staying generic.
58
+
59
+ The middleware supports both synchronous (WSGI) and asynchronous (ASGI)
60
+ views. It auto-detects the view chain at startup and routes requests through
61
+ the matching path, so no extra configuration is required for either server
62
+ type. Tracebacks are sanitized before leaving the process, and API calls are
63
+ rate limited.
64
+
65
+ ## Scope
66
+
67
+ This package explains errors for a person, not for a coding agent to consume
68
+ programmatically. The explanation is written for a human reader, in a terminal or an editor's
69
+ integrated terminal, and the output format assumes that reader.
70
+
71
+ If a coding agent is doing the debugging, it does not need this. Agents read tracebacks directly,
72
+ and tools that expose live runtime state (debugger-over-MCP servers, `mcp-django`) serve that case
73
+ better. This package is not trying to compete there.
74
+
75
+ Local development only. It requires `DEBUG = True` and is inert otherwise.
76
+
77
+ ## Features
78
+
79
+ - Captures Django errors and exceptions
80
+ - Explains errors using OpenAI, Anthropic's Claude models, or any other
81
+ OpenAI-compatible endpoint (Ollama, LM Studio, Azure, gateways) via `OPENAI_BASE_URL`
82
+ - Optional codebase-aware explanations (RAG) backed by a local sqlite-vec index (see the RAG section below)
83
+ - Explanations in your language via `EXPLAIN_ERRORS_LANGUAGE`, with exception names,
84
+ identifiers, and code kept in English
85
+ - Redacts secrets, tokens, and emails from tracebacks before sending
86
+ - Rate limits API calls with a configurable sliding window
87
+ - Works with both sync (WSGI) and async (ASGI) views
88
+ - Manages the API key using environment variables
89
+
90
+ ## Installation
91
+
92
+ 1. Install django-explain-errors by running:
93
+ ```bash
94
+ pip install django-explain-errors
95
+ ```
96
+
97
+ 2. **Add the middleware to your Django project**:
98
+
99
+ - Open your `settings.py` file and add the middleware to the `MIDDLEWARE` list:
100
+
101
+ ```python
102
+ MIDDLEWARE = [
103
+ ...
104
+ 'explain_errors.middleware.ExplainErrorsMiddleware',
105
+ ]
106
+ ```
107
+
108
+ In the default preserve mode, `process_exception` returns `None`, so exception handling
109
+ continues normally no matter where the middleware sits in the list — it no longer needs
110
+ to be last to avoid pre-empting other packages' error handling. It still needs to sit
111
+ close enough to the view that unhandled exceptions actually reach it, before any other
112
+ middleware that might catch and handle them itself. If you set
113
+ `EXPLAIN_ERRORS_PRESERVE_DEBUG_PAGE=False`, keep it last: returning a response there still
114
+ ends exception handling early and pre-empts anything above it in the stack (Debug Toolbar,
115
+ Sentry, Rollbar — see Compatibility below).
116
+
117
+ 3. **Set up environment variables**:
118
+
119
+ - Create a `.env` file in your project's root directory and add your OpenAI API key. Alternatively, you can set the API key in `settings.py`:
120
+
121
+ ```plaintext
122
+ OPENAI_API_KEY=your_openai_api_key_here
123
+ ```
124
+
125
+ The API key is not required if you set `OPENAI_BASE_URL` to a local
126
+ server such as Ollama, which does not authenticate requests.
127
+
128
+ ## Usage
129
+
130
+ 1. **Ensure DEBUG is set to True**:
131
+
132
+ Open your `settings.py` file and set:
133
+
134
+ ```python
135
+ DEBUG = True
136
+ ```
137
+
138
+ 2. **Trigger an error in your Django application**:
139
+
140
+ The middleware captures the error, sends it to the configured model for explanation, and prints the explanation to stdout. By default (`EXPLAIN_ERRORS_PRESERVE_DEBUG_PAGE=True`), it then lets exception handling continue normally, so Django (or whatever else is watching, such as `runserver_plus` or Sentry — see Compatibility below) renders exactly what it would without this middleware installed. Set `EXPLAIN_ERRORS_PRESERVE_DEBUG_PAGE = False` to instead get a JSON `500` response containing the error message and the explanation.
141
+
142
+ ## Async Support
143
+
144
+ 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:
145
+
146
+ - Under WSGI (for example `runserver` with sync views), requests flow through the synchronous handler.
147
+ - 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_async` so the event loop is not blocked.
148
+
149
+ No additional settings are needed. See Installation above for where to place the middleware in `MIDDLEWARE`.
150
+
151
+ ## Compatibility
152
+
153
+ How this middleware interacts with other error-handling and debugging tools, in the default
154
+ preserve mode and with `EXPLAIN_ERRORS_PRESERVE_DEBUG_PAGE=False`:
155
+
156
+ | Package | Preserve mode (default) | `EXPLAIN_ERRORS_PRESERVE_DEBUG_PAGE=False` |
157
+ | ------- | ------------------------ | ------------------------------------------- |
158
+ | Django Debug Toolbar | Works — Django renders its normal debug page, and Debug Toolbar injects into it. | Broken — the JSON 500 response has no HTML to inject into. |
159
+ | Sentry, Rollbar | Work — the exception propagates and Django re-raises it, so `got_request_exception` fires. | Broken — returning a response ends exception handling before `got_request_exception` fires. |
160
+ | Django REST Framework | Partial — only exceptions DRF does not already handle itself reach this middleware. | Partial, same reason. |
161
+ | Silk and other profiling panels | Timings are inflated by the OpenAI call, since `process_exception` blocks the request path. | Same. |
162
+ | CORS, GZip, WhiteNoise | No interaction. | No interaction. |
163
+ | `runserver_plus` / Werkzeug debugger | Works — returning `None` re-raises the original exception, and `django-extensions` replaces Django's debug-page renderer with one that re-raises instead, so Werkzeug's WSGI wrapper catches it and shows the interactive debugger. | Broken — the JSON 500 response ends exception handling before it reaches `runserver_plus`'s exception hook, so the Werkzeug debugger never appears. |
164
+
165
+ Debug Toolbar, Sentry, and Werkzeug were verified empirically, on both Django 4.2 and Django
166
+ 6.1, in both modes. DRF, Silk, and CORS/GZip/WhiteNoise are reasoned from the mechanism
167
+ rather than tested.
168
+
169
+ One additional behavior worth knowing, also verified on both Django versions: when the
170
+ explanation call itself fails (a bad key, a timeout, an unreachable endpoint), a
171
+ Sentry-instrumented project captures a separate event for that failure. `explain_errors`
172
+ catches the exception internally, so it never becomes an unhandled exception, but Sentry's
173
+ `httpx` integration captures it anyway by instrumenting inside the HTTP client library rather
174
+ than relying on `got_request_exception`. That event is unrelated to whatever error the
175
+ developer is actually investigating.
176
+
177
+ ## Configuration
178
+
179
+ | Setting / variable | Required | Description |
180
+ | ------------------ | -------- | ----------- |
181
+ | `OPENAI_API_KEY` (env or settings) | Yes, unless `OPENAI_BASE_URL` points at an endpoint that does not authenticate | API key used to authenticate with the configured endpoint. Read first from the environment, then from `settings`. |
182
+ | `DEBUG` | Yes | The middleware is only active when `DEBUG=True`. When `False`, requests pass through untouched. |
183
+ | `OPENAI_MODEL` | No | Model used for explanations. Defaults to `gpt-4o-mini`. |
184
+ | `OPENAI_MAX_TOKENS` | No | Ceiling on tokens generated for the explanation, not a target — the system prompt itself asks for a concise answer. Defaults to `1000`; scales up automatically when `EXPLAIN_ERRORS_LANGUAGE` is set (see below), unless you set this explicitly, which always overrides the scaling. |
185
+ | `OPENAI_TIMEOUT` | No | Request timeout in seconds for the OpenAI client. Defaults to `10`. |
186
+ | `OPENAI_MAX_TRACEBACK_CHARS` | No | Traceback is trimmed to its last N characters before being sent. Defaults to `3000`. |
187
+ | `EXPLAIN_ERRORS_PRESERVE_DEBUG_PAGE` | No | When `True` (the default), the middleware prints the explanation to stdout and returns `None`, so exception handling continues normally and Django renders its standard debug page. Set to `False` to instead return a JSON 500 response, which ends exception handling early (see Compatibility above). |
188
+ | `OPENAI_BASE_URL` (env or settings) | No | Base URL for any OpenAI-compatible API (for example Ollama at `http://localhost:11434/v1`). When set, a missing API key is replaced with a placeholder since local servers do not require one. |
189
+ | `EXPLAIN_ERRORS_MAX_CALLS` | No | Together with `EXPLAIN_ERRORS_WINDOW_SECONDS`, caps API spend to at most this many explanations within a rolling window; once the cap is hit, further errors in that window are not sent for explanation until an earlier call ages out. Defaults to `5`. |
190
+ | `EXPLAIN_ERRORS_WINDOW_SECONDS` | No | Length in seconds of the rolling window `EXPLAIN_ERRORS_MAX_CALLS` is measured against. Defaults to `60` (with the defaults, at most 5 explanations per 60-second window). |
191
+ | `EXPLAIN_ERRORS_REDACT_PATTERNS` | No | Extra regex pattern strings (each passed to `re.compile`) applied to the traceback, appended after the built-in secret/token/email patterns. An invalid pattern is skipped with a warning rather than raising. Defaults to `[]`. |
192
+ | `EXPLAIN_ERRORS_REDACT_DISABLE_DEFAULTS` | No | When `True`, skips the built-in secret/token/email redaction patterns entirely and redacts only what `EXPLAIN_ERRORS_REDACT_PATTERNS` specifies. Turning this on removes the default protection against leaking secrets and PII in tracebacks. Defaults to `False`. |
193
+ | `EXPLAIN_ERRORS_REDACT_REPLACEMENT` | No | Replacement string substituted for anything matched by the redaction patterns. Defaults to `"[REDACTED]"`. |
194
+ | `EXPLAIN_ERRORS_LANGUAGE` | No | Language the explanation prose is written in, as a plain name or code (for example `"Spanish"` or `"es"`). Defaults to `None`, meaning English. Exception names, identifiers, code, file paths, and tracebacks always stay in English regardless of this setting. |
195
+
196
+ ## Using local models (Ollama)
197
+
198
+ Point `OPENAI_BASE_URL` at any OpenAI-compatible server to run explanations
199
+ against a local model instead of the OpenAI API:
200
+
201
+ ```python
202
+ OPENAI_BASE_URL = "http://localhost:11434/v1"
203
+ OPENAI_MODEL = "llama3.1"
204
+ EXPLAIN_ERRORS_RAG_EMBED_MODEL = "nomic-embed-text"
205
+ ```
206
+
207
+ With a local endpoint, no traceback or source code leaves your machine,
208
+ which matters if you work somewhere that cannot send code to a
209
+ third-party API.
210
+
211
+ If you use the RAG layer, rebuild the index after changing the embedding
212
+ model or provider. Stored vectors are model-specific.
213
+
214
+ ## Using Anthropic (Claude) models
215
+
216
+ Anthropic's Claude models work today through Anthropic's OpenAI-compatible API, with no
217
+ Anthropic-specific code required:
218
+
219
+ ```python
220
+ OPENAI_BASE_URL = "https://api.anthropic.com/v1/"
221
+ OPENAI_API_KEY = "your_anthropic_api_key_here"
222
+ OPENAI_MODEL = "..." # see Anthropic's current model list below
223
+ ```
224
+
225
+ `OPENAI_MODEL` is mandatory here: the default (`gpt-4o-mini`) doesn't exist on Anthropic's API
226
+ and will 404. Use one of the model names from
227
+ [Anthropic's model overview](https://platform.claude.com/docs/en/about-claude/models/overview);
228
+ model names are versioned and retired over time, so check that page rather than relying on a
229
+ name pinned here.
230
+
231
+ Anthropic documents this compatibility layer as intended primarily for testing and comparing
232
+ model capabilities, not as a production integration path. That's an acceptable tradeoff for a
233
+ development-only middleware, but worth knowing going in.
234
+
235
+ Reasoning models behind `OPENAI_BASE_URL` spend part of the token budget on internal reasoning
236
+ before producing visible output. At a low `OPENAI_MAX_TOKENS`, the budget can be used up by
237
+ reasoning alone, and the explanation comes back empty. Raise `OPENAI_MAX_TOKENS` if you see this.
238
+
239
+ ### A note on API keys and 401s
240
+
241
+ `explain_errors` reads `OPENAI_API_KEY` (see Configuration above); it does not read
242
+ provider-specific variables such as `ANTHROPIC_API_KEY`. If no key is found and
243
+ `OPENAI_BASE_URL` is set, the client substitutes a placeholder key rather than raising an
244
+ error — a convenience for local servers like Ollama or LM Studio, which ignore the key
245
+ entirely. Against a real remote endpoint such as Anthropic's, that placeholder is sent as-is
246
+ and rejected, so a missing `OPENAI_API_KEY` shows up as an opaque `401 Unauthorized` rather
247
+ than a clear configuration error. If you see a 401 with `OPENAI_BASE_URL` pointed at a remote
248
+ provider, check that `OPENAI_API_KEY` — not a provider-specific variable — is actually set.
249
+
250
+ ## Explanation language
251
+
252
+ By default, explanations are written in English. Set `EXPLAIN_ERRORS_LANGUAGE` to
253
+ read them in another language instead:
254
+
255
+ ```python
256
+ EXPLAIN_ERRORS_LANGUAGE = "Spanish" # or the code form, "es"
257
+ ```
258
+
259
+ This is independent of Django's own `LANGUAGE_CODE`, which controls the language your
260
+ site serves to its users, not the language you read explanations in. Regardless of
261
+ `EXPLAIN_ERRORS_LANGUAGE`, exception type names, Django and Python identifiers, code,
262
+ file paths, and tracebacks are always kept in English, so they stay greppable and
263
+ matchable against documentation and search results. Only the explanatory prose is
264
+ translated.
265
+
266
+ **Known limitation:** small local models behind `OPENAI_BASE_URL` (see "Using local
267
+ models" above) tend to degrade sharply outside English. Output quality with
268
+ `EXPLAIN_ERRORS_LANGUAGE` set does not transfer uniformly across providers — it is
269
+ generally solid against OpenAI and Anthropic's APIs, but a small local model that
270
+ writes fluent English explanations may produce broken or mixed-language output once
271
+ asked to switch languages.
272
+
273
+ When a language is configured, the token ceiling (`OPENAI_MAX_TOKENS`) is scaled up
274
+ automatically so explanations in languages that tokenize less efficiently than English
275
+ aren't cut off mid-sentence. This scaling is deliberately generous — billing follows
276
+ tokens actually generated, so unused headroom costs nothing — rather than precise: the
277
+ underlying tokens-per-word figures are estimates, not direct measurements, and the
278
+ default model (`gpt-4o-mini`) uses the `o200k_base` tokenizer, which handles non-Latin
279
+ scripts considerably better than the `cl100k_base`-era ratios these estimates lean on.
280
+
281
+ ## Codebase-aware explanations (RAG)
282
+
283
+ By default, explanations are generated from the traceback alone. With the
284
+ optional RAG (retrieval-augmented generation) layer enabled, the middleware
285
+ also retrieves the most relevant chunks of your own project's source code
286
+ from a local vector index and includes them in the prompt, so explanations
287
+ can reference your actual functions and classes instead of guessing at them.
288
+
289
+ This feature is opt-in and adds no dependencies or behavior unless enabled.
290
+
291
+ ### Install the extra
292
+
293
+ ```bash
294
+ pip install django-explain-errors[rag]
295
+ ```
296
+
297
+ This pulls in [sqlite-vec](https://github.com/asg017/sqlite-vec), a
298
+ single-file, no-server vector store. The core package stays dependency-light
299
+ if you don't need RAG.
300
+
301
+ ### Build the index
302
+
303
+ Add `explain_errors` to `INSTALLED_APPS` (needed for Django to discover the
304
+ management command), then run:
305
+
306
+ ```bash
307
+ python manage.py build_error_index
308
+ ```
309
+
310
+ This walks your project, chunks Python files by top-level function/class
311
+ (and other text files by fixed-size line windows), embeds each chunk with
312
+ the OpenAI embeddings API, and writes them to a local index file. Re-run it
313
+ whenever your source changes meaningfully. Indexing is not automatic.
314
+ Rebuilding is idempotent: it builds into a temp file and atomically replaces
315
+ the previous index.
316
+
317
+ ### Enable it
318
+
319
+ ```python
320
+ # settings.py
321
+
322
+ EXPLAIN_ERRORS_RAG_ENABLED = True
323
+ ```
324
+
325
+ ### Settings
326
+
327
+ | Setting | Default | Description |
328
+ | ------- | ------- | ----------- |
329
+ | `EXPLAIN_ERRORS_RAG_ENABLED` | `False` | Master switch for the RAG layer. |
330
+ | `EXPLAIN_ERRORS_RAG_INDEX_PATH` | `<BASE_DIR>/.explain_errors_index.db` | Path to the local vector index file. |
331
+ | `EXPLAIN_ERRORS_RAG_TOP_K` | `4` | Number of chunks retrieved and injected into the prompt. |
332
+ | `EXPLAIN_ERRORS_RAG_EMBED_MODEL` | `"text-embedding-3-small"` | OpenAI embedding model used for indexing and retrieval. |
333
+ | `EXPLAIN_ERRORS_RAG_INCLUDE` | `None` (defaults to `BASE_DIR`) | List of directories to index. |
334
+ | `EXPLAIN_ERRORS_RAG_EXCLUDE` | migrations, venvs, `node_modules`, static, media, `.git` | Directory names to skip while indexing. |
335
+ | `EXPLAIN_ERRORS_RAG_MAX_PROMPT_CHARS` | `6000` | Combined character budget for the traceback + retrieved source sections of the prompt. |
336
+
337
+ Every chunk of source code and every retrieval query is passed through the
338
+ same `sanitize_traceback()` redaction used for tracebacks, which strips
339
+ patterns that look like secrets, tokens, and emails before anything is sent
340
+ to OpenAI or written to the index. It's a pattern-based filter, not a
341
+ guarantee: it catches recognizable secret shapes, not arbitrary sensitive
342
+ data that doesn't match one.
343
+
344
+ If RAG is enabled but the index is missing, `sqlite-vec` isn't installed, or
345
+ retrieval fails for any reason, the middleware logs a warning and falls back
346
+ to the traceback-only prompt. It never breaks error reporting.
347
+
348
+ RAG-grounded explanations tend to be longer than traceback-only ones. The default `OPENAI_MAX_TOKENS` already leaves generous headroom for this, but if you've lowered it, raise it back up when RAG is enabled so explanations are not truncated.
349
+
350
+ ### .gitignore
351
+
352
+ The index file is a local build artifact, not something to commit. Add it
353
+ to your project's `.gitignore`:
354
+
355
+ ```
356
+ .explain_errors_index.db
357
+ ```
358
+
359
+ (Adjust the path if you set `EXPLAIN_ERRORS_RAG_INDEX_PATH` to something
360
+ else.)
361
+
362
+ ### Before / after
363
+
364
+ **Without RAG**, traceback only:
365
+
366
+ > Your `ValueError` is raised because the value passed to `foo()` couldn't
367
+ > be converted to an integer. Check where `foo()` is called and make sure
368
+ > you're passing a numeric string.
369
+
370
+ **With RAG**, grounded in the actual function:
371
+
372
+ > In `myapp/utils.py`, `foo()` calls `int(value)` on line 12 without a
373
+ > `try`/`except`, so any non-numeric `value` raises `ValueError` straight
374
+ > through to the caller. Since `foo()` is called from `myapp/views.py` with
375
+ > unvalidated form input, add validation there or wrap the `int()` call in
376
+ > `foo()` with a clear error message.
377
+
378
+ ## Example
379
+
380
+ Here is an example of how to use the middleware in a Django project:
381
+
382
+ ```python
383
+ # settings.py
384
+
385
+ DEBUG = True
386
+
387
+ MIDDLEWARE = [
388
+ ...
389
+ 'explain_errors.middleware.ExplainErrorsMiddleware',
390
+ ]
391
+
392
+ # .env
393
+
394
+ OPENAI_API_KEY=your_openai_api_key_here
395
+ ```
396
+
397
+ When an error occurs, you will see an explanation printed to stdout.
398
+
399
+ ## License
400
+
401
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
402
+
403
+ ## Contributing
404
+
405
+ Contributions are welcome! Please open an issue or submit a pull request for any improvements or bug fixes.
406
+
407
+ ## Acknowledgements
408
+
409
+ - [Django](https://www.djangoproject.com/)
410
+ - [OpenAI](https://www.openai.com/)
411
+ - [python-dotenv](https://github.com/theskumar/python-dotenv)