198 lines
6.4 KiB
Markdown
198 lines
6.4 KiB
Markdown
|
|
---
|
|||
|
|
title: Debugging Instructor Applications
|
|||
|
|
description: Learn how to debug Instructor applications with hooks, logging, and exception handling. Practical techniques for inspecting inputs, outputs, and retries.
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Debugging
|
|||
|
|
|
|||
|
|
This guide shows how to quickly inspect inputs/outputs, capture retries, and reproduce failures when working with Instructor. It focuses on practical techniques using hooks, logging, and exception data.
|
|||
|
|
|
|||
|
|
## Enable Logs
|
|||
|
|
|
|||
|
|
### Quick Debug Mode (Recommended)
|
|||
|
|
|
|||
|
|
The fastest way to enable debug logging is with the `INSTRUCTOR_DEBUG` environment variable:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
export INSTRUCTOR_DEBUG=1
|
|||
|
|
python your_script.py
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Or inline:
|
|||
|
|
```bash
|
|||
|
|
INSTRUCTOR_DEBUG=1 python your_script.py
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This automatically enables debug logging with correlation IDs for request tracing.
|
|||
|
|
|
|||
|
|
### Manual Debug Configuration
|
|||
|
|
|
|||
|
|
You can also use the standard Python `logging` module for more control:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import logging
|
|||
|
|
logging.basicConfig(level=logging.DEBUG)
|
|||
|
|
logging.getLogger("instructor").setLevel(logging.DEBUG)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
You will see messages for:
|
|||
|
|
- Raw responses (provider-specific objects)
|
|||
|
|
- Handler/mode selection
|
|||
|
|
- Retry attempts and parse errors
|
|||
|
|
- Reask adjustments to `messages`
|
|||
|
|
- **Correlation IDs** for tracing requests (format: `[a1b2c3d4]`)
|
|||
|
|
|
|||
|
|
Tip: Set a handler/formatter to include timestamps and module names.
|
|||
|
|
|
|||
|
|
## Observe the Flow with Hooks
|
|||
|
|
|
|||
|
|
Hooks let you tap into key moments without modifying core code:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from instructor.core.hooks import HookName
|
|||
|
|
|
|||
|
|
# Attach one or more handlers
|
|||
|
|
client.on(HookName.COMPLETION_KWARGS, lambda **kw: print("KWARGS:", kw))
|
|||
|
|
client.on(HookName.COMPLETION_RESPONSE, lambda resp: print("RESPONSE:", type(resp)))
|
|||
|
|
client.on(HookName.PARSE_ERROR, lambda e: print("PARSE ERROR:", e))
|
|||
|
|
client.on(HookName.COMPLETION_LAST_ATTEMPT, lambda e: print("LAST ATTEMPT:", e))
|
|||
|
|
client.on(HookName.COMPLETION_ERROR, lambda e: print("COMPLETION ERROR:", e))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Common uses:
|
|||
|
|
- Capture the final `kwargs` passed to the provider (including mode/tools/response_format).
|
|||
|
|
- Record raw responses (e.g., to logs or a file) for offline analysis.
|
|||
|
|
- Inspect parse errors and how reask modifies the next attempt.
|
|||
|
|
|
|||
|
|
Note: Handlers that accept `**kwargs` (or a parameter named `_instructor_meta`) receive a metadata dict with:
|
|||
|
|
- `attempt_number`, `correlation_id`, `mode`, `response_model_name`.
|
|||
|
|
Add `**kwargs` to your handler signature to access it:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
client.on(HookName.COMPLETION_KWARGS, lambda **kw: print(kw.get("_instructor_meta")))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Inspect Raw Responses
|
|||
|
|
|
|||
|
|
Most parsed models returned by Instructor carry the original provider response for debugging:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
model = client.create(...)
|
|||
|
|
raw = getattr(model, "_raw_response", None)
|
|||
|
|
print(raw)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This is useful for checking provider metadata like token usage, model version, and provider-specific fields.
|
|||
|
|
|
|||
|
|
## Handling Failures & Retries
|
|||
|
|
|
|||
|
|
When all retries are exhausted, an `InstructorRetryException` is raised. It includes detailed context:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from instructor.core.exceptions import InstructorRetryException
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client.create(...)
|
|||
|
|
except InstructorRetryException as e:
|
|||
|
|
print("Attempts:", e.n_attempts)
|
|||
|
|
print("Last completion:", e.last_completion)
|
|||
|
|
print("Create kwargs:", e.create_kwargs) # reproducible input
|
|||
|
|
print("Failed attempts:", e.failed_attempts) # list of (attempt, exception, completion)
|
|||
|
|
# If available, a compact trace packet to help debugging
|
|||
|
|
if hasattr(e, "trace_packet") and e.trace_packet:
|
|||
|
|
print("Trace packet:", e.trace_packet)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Use `e.create_kwargs` and `e.failed_attempts` to craft a minimal reproduction.
|
|||
|
|
|
|||
|
|
## Minimal Reproduction Template
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import openai
|
|||
|
|
import instructor
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
|
|||
|
|
class MyModel(BaseModel):
|
|||
|
|
# fields...
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
client = instructor.from_provider("openai/gpt-5-nano")
|
|||
|
|
|
|||
|
|
create_kwargs = {
|
|||
|
|
# paste from InstructorRetryException.create_kwargs
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client.create(response_model=MyModel, **create_kwargs)
|
|||
|
|
except Exception as err:
|
|||
|
|
# Inspect and iterate
|
|||
|
|
raise
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This pattern captures the exact inputs that triggered a failure.
|
|||
|
|
|
|||
|
|
## Strict vs Non-Strict Parsing
|
|||
|
|
|
|||
|
|
- `strict=True` enforces exact schema matches and can surface schema drift early.
|
|||
|
|
- If providers sometimes return extra fields or slightly different types (e.g., floats for ints), try `strict=False` to validate non‑strictly.
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
client.create(..., response_model=MyModel, strict=True)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Customizing Retries
|
|||
|
|
|
|||
|
|
You can pass an integer (attempt count) or a `tenacity` retrying object to control behavior:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from tenacity import Retrying, stop_after_attempt, stop_after_delay
|
|||
|
|
|
|||
|
|
max_retries = Retrying(stop=stop_after_attempt(3) | stop_after_delay(10))
|
|||
|
|
client.create(..., max_retries=max_retries)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This is helpful when balancing latency and robustness.
|
|||
|
|
|
|||
|
|
## Multimodal & Message Conversion
|
|||
|
|
|
|||
|
|
If you send images/audio/PDFs or text that may include media paths/URIs, Instructor can convert messages for provider formats.
|
|||
|
|
|
|||
|
|
- For supported modes, `processing.multimodal.convert_messages` runs automatically.
|
|||
|
|
- If debugging content issues, log `messages` before and after conversion using the hooks above, and ensure media types/URIs are valid.
|
|||
|
|
|
|||
|
|
## Caching Considerations
|
|||
|
|
|
|||
|
|
If you’re using a cache (`cache=...`), remember:
|
|||
|
|
- Successful parsed responses are stored; retrieving from cache skips the provider call.
|
|||
|
|
- If debugging live provider behavior, temporarily disable cache or change the cache key (e.g., tweak a message).
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
model = client.create(..., cache=None)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Common Troubleshooting Tips
|
|||
|
|
|
|||
|
|
- Validate the `response_model.model_json_schema()` matches what you expect the provider to return.
|
|||
|
|
- Confirm `mode` is valid for your provider; mismatches can cause parsing failures.
|
|||
|
|
- Check provider‑side limits (max tokens/response length); incomplete outputs raise specific exceptions.
|
|||
|
|
- If using markdown JSON (`MD_JSON`), ensure the provider is actually returning a ```json code block.
|
|||
|
|
|
|||
|
|
If you need deeper visibility, add a custom handler to write kwargs/responses/errors to disk with a timestamp and correlation id.
|
|||
|
|
|
|||
|
|
## Example: Local Debug Run
|
|||
|
|
|
|||
|
|
You can run a minimal, no‑network example that exercises hooks, logging, and parsing flow using a fake provider function:
|
|||
|
|
|
|||
|
|
- File: `examples/debugging/run.py`
|
|||
|
|
- Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python examples/debugging/run.py
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This script:
|
|||
|
|
- Enables DEBUG logging for `instructor.*`
|
|||
|
|
- Patches a fake provider `create` with `instructor.patch(mode=Mode.JSON)`
|
|||
|
|
- Attaches hook handlers to print kwargs, response types, and parse errors
|
|||
|
|
- Parses a simple JSON payload into a Pydantic model and prints the result
|