Skip to content

Commit 267282f

Browse files
committed
feat(v2): introduce core architecture documentation and exception handling
- Add comprehensive README.md for v2 core architecture detailing registry-based design, exception handling, and component interactions. - Implement centralized exception handling with new exception classes: RegistryError and ValidationContextError. - Introduce RegistryValidationMixin for validating mode registration and context parameters. - Update patch and retry logic to utilize new exception handling mechanisms. This commit lays the groundwork for the v2 architecture, enhancing modularity and error management.
1 parent 743d009 commit 267282f

File tree

4 files changed

+501
-40
lines changed

4 files changed

+501
-40
lines changed

instructor/v2/README.md

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
# V2 Core Architecture
2+
3+
This document covers the v2 core infrastructure, including the registry-based design, exception handling, and component interactions.
4+
5+
## Overview
6+
7+
The v2 architecture uses a hierarchical registry system for managing provider modes and their corresponding handlers. It replaces the monolithic v1 approach with modular, composable components:
8+
9+
- **Registry**: Central mode/handler management
10+
- **Handlers**: Pluggable request/response/reask handlers per mode
11+
- **Patch**: Unified function patching mechanism
12+
- **Retry**: Intelligent retry with registry-based handling
13+
- **Exceptions**: Organized, centralized error handling
14+
15+
## Core Components
16+
17+
### Protocols (`instructor/v2/core/protocols.py`)
18+
19+
Type-safe interfaces for handlers:
20+
21+
- `RequestHandler` - Prepares request kwargs for a mode
22+
- `ResponseParser` - Parses API response into Pydantic model
23+
- `ReaskHandler` - Handles validation failures for retry
24+
25+
### Mode Registry (`instructor/v2/core/registry.py`)
26+
27+
The mode registry manages all available modes for each provider. It maps `(Provider, Mode)` tuples to their handler implementations.
28+
29+
**Key Features**:
30+
31+
- Provider/mode combination lookup
32+
- Handler registration and retrieval
33+
- Mode listing and discovery
34+
- Fast O(1) lookups for handler dispatch
35+
36+
**Registry API**:
37+
38+
```python
39+
from instructor.v2.core.registry import mode_registry
40+
from instructor import Provider, Mode
41+
42+
# Get handlers (preferred)
43+
handlers = mode_registry.get_handlers(Provider.ANTHROPIC, Mode.TOOLS)
44+
45+
# Query
46+
modes = mode_registry.get_modes_for_provider(Provider.ANTHROPIC)
47+
is_registered = mode_registry.is_registered(Provider.ANTHROPIC, Mode.TOOLS)
48+
```
49+
50+
Handlers are registered via `@register_mode_handler` decorator (see Handler Registration).
51+
52+
### Patch Mechanism (`instructor/v2/core/patch.py`)
53+
54+
Wraps provider API functions to add structured output support. Auto-detects sync/async, validates mode registration, injects default models, and integrates with registry handlers.
55+
56+
```python
57+
from instructor.v2.core.patch import patch_v2
58+
59+
patched_create = patch_v2(
60+
client.messages.create,
61+
provider=Provider.ANTHROPIC,
62+
mode=Mode.TOOLS,
63+
default_model="claude-3-5-sonnet-20241022"
64+
)
65+
```
66+
67+
### Retry Logic (`instructor/v2/core/retry.py`)
68+
69+
Handles retries with registry-based reask logic. On `ValidationError`, uses registry handlers to generate reask prompts and retries up to `max_retries` times.
70+
71+
## Exception Handling
72+
73+
V2 exceptions inherit from `instructor.core.exceptions.InstructorError`:
74+
75+
- `RegistryError` - Mode not registered or handler lookup failure
76+
- `ValidationContextError` - Conflicting `context`/`validation_context` parameters
77+
- `InstructorRetryException` - Max retries exceeded with full attempt context
78+
79+
`RegistryValidationMixin` provides validation utilities used internally.
80+
81+
## Handler System
82+
83+
Handlers are pluggable components that implement provider-specific logic. They can be implemented as classes (using `ModeHandler` ABC) or as standalone functions (using Protocols).
84+
85+
### Handler Base Class (`instructor/v2/core/handler.py`)
86+
87+
The `ModeHandler` abstract base class provides a structured way to implement handlers:
88+
89+
```python
90+
from instructor.v2.core.handler import ModeHandler
91+
from pydantic import BaseModel
92+
from typing import Any
93+
94+
class MyModeHandler(ModeHandler):
95+
"""Handler for a specific mode."""
96+
97+
def prepare_request(
98+
self,
99+
response_model: type[BaseModel] | None,
100+
kwargs: dict[str, Any],
101+
) -> tuple[type[BaseModel] | None, dict[str, Any]]:
102+
"""Prepare request kwargs for this mode."""
103+
# Modify kwargs for mode-specific requirements
104+
return response_model, kwargs
105+
106+
def handle_reask(
107+
self,
108+
kwargs: dict[str, Any],
109+
response: Any,
110+
exception: Exception,
111+
) -> dict[str, Any]:
112+
"""Handle validation failure and prepare retry."""
113+
# Modify kwargs for retry attempt
114+
return kwargs
115+
116+
def parse_response(
117+
self,
118+
response: Any,
119+
response_model: type[BaseModel],
120+
validation_context: dict[str, Any] | None = None,
121+
strict: bool | None = None,
122+
) -> BaseModel:
123+
"""Parse API response into validated Pydantic model."""
124+
# Extract and validate response
125+
return response_model.model_validate(...)
126+
```
127+
128+
### Handler Registration
129+
130+
All handlers must be registered using the `@register_mode_handler` decorator. This is the **only supported way** to register handlers in v2.
131+
132+
```python
133+
from instructor.v2.core.decorators import register_mode_handler
134+
from instructor import Provider, Mode
135+
from instructor.v2.core.handler import ModeHandler
136+
137+
@register_mode_handler(Provider.ANTHROPIC, Mode.TOOLS)
138+
class AnthropicToolsHandler(ModeHandler):
139+
"""Handler automatically registered on import.
140+
141+
The decorator internally calls mode_registry.register() with the
142+
handler methods mapped to the protocol functions.
143+
"""
144+
145+
def prepare_request(self, response_model, kwargs):
146+
# Implementation
147+
return response_model, kwargs
148+
149+
def handle_reask(self, kwargs, response, exception):
150+
# Implementation
151+
return kwargs
152+
153+
def parse_response(self, response, response_model, **kwargs):
154+
# Implementation
155+
return response_model.model_validate(...)
156+
```
157+
158+
**How it works**: The decorator instantiates the handler class and calls `mode_registry.register()` with the handler's methods mapped to the protocol functions:
159+
160+
- `handler.prepare_request``request_handler`
161+
- `handler.handle_reask``reask_handler`
162+
- `handler.parse_response``response_parser`
163+
164+
**Benefits**:
165+
166+
- Automatic registration on import (no manual calls needed)
167+
- Clean, declarative syntax
168+
- Type-safe and consistent with the codebase pattern
169+
- Used by all v2 providers (see `instructor/v2/providers/anthropic/handlers.py`)
170+
171+
**Important**: Direct calls to `mode_registry.register()` are not supported. All handlers must use the `@register_mode_handler` decorator.
172+
173+
## Execution Flow
174+
175+
### Sync Execution Path
176+
177+
```text
178+
Client.create() with response_model
179+
180+
patch_v2() [registry validation]
181+
182+
new_create_sync()
183+
├─ handle_context() [parameter validation]
184+
└─ retry_sync_v2() [retry logic]
185+
├─ validate_mode_registration()
186+
├─ For each attempt:
187+
│ ├─ Call original API
188+
│ ├─ Get handlers from registry
189+
│ ├─ Parse response via handler
190+
│ ├─ On success → return
191+
│ └─ On ValidationError:
192+
│ ├─ Record attempt
193+
│ ├─ Get reask via handler
194+
│ └─ Retry
195+
└─ Max retries exceeded → InstructorRetryException
196+
```
197+
198+
### Async Execution Path
199+
200+
```text
201+
AsyncClient.create() with response_model
202+
203+
patch_v2() [registry validation]
204+
205+
new_create_async()
206+
├─ handle_context() [parameter validation]
207+
└─ retry_async_v2() [async retry logic]
208+
├─ validate_mode_registration()
209+
├─ For each attempt:
210+
│ ├─ Await API call
211+
│ ├─ Get handlers from registry
212+
│ ├─ Parse response via handler
213+
│ ├─ On success → return
214+
│ └─ On ValidationError:
215+
│ ├─ Record attempt
216+
│ ├─ Get reask via handler
217+
│ └─ Retry
218+
└─ Max retries exceeded → InstructorRetryException
219+
```
220+
221+
## Error Handling Strategy
222+
223+
- **Fail fast**: Mode validation at patch time
224+
- **Context validation**: `context`/`validation_context` conflict detection
225+
- **Comprehensive logging**: All stages logged with attempt numbers
226+
- **Exception chaining**: Full context preserved in exception chain
227+
228+
## Configuration
229+
230+
- **Mode**: Specified when creating client (`from_anthropic(client, mode=Mode.TOOLS)`)
231+
- **Default Model**: Injected via `patch_v2(..., default_model="...")` if not provided in request
232+
- **Max Retries**: Per-request via `max_retries=3` or `Retrying(...)` instance
233+
234+
## Adding a New Provider
235+
236+
1. **Add Provider Enum** (`instructor/utils.py`):
237+
238+
```python
239+
class Provider(Enum):
240+
YOUR_PROVIDER = "your_provider"
241+
```
242+
243+
2. **Create Handler** (`instructor/v2/providers/your_provider/handlers.py`):
244+
245+
```python
246+
from instructor.v2.core.handler import ModeHandler
247+
from instructor.v2.core.decorators import register_mode_handler
248+
from instructor import Provider, Mode
249+
250+
@register_mode_handler(Provider.YOUR_PROVIDER, Mode.TOOLS)
251+
class YourProviderToolsHandler(ModeHandler):
252+
def prepare_request(self, response_model, kwargs):
253+
# Convert response_model to provider tools format
254+
return response_model, kwargs
255+
256+
def parse_response(self, response, response_model, **kwargs):
257+
# Extract and validate response
258+
return response_model.model_validate(...)
259+
260+
def handle_reask(self, kwargs, response, exception):
261+
# Add error message for retry
262+
return kwargs
263+
```
264+
265+
3. **Create Factory** (`instructor/v2/providers/your_provider/client.py`):
266+
267+
```python
268+
from instructor.v2.providers.your_provider import handlers # noqa: F401
269+
from instructor.v2.core.patch import patch_v2
270+
from instructor import Instructor, AsyncInstructor, Mode, Provider
271+
272+
@overload
273+
def from_your_provider(client: YourProviderClient, mode=Mode.TOOLS) -> Instructor: ...
274+
275+
def from_your_provider(client, mode=Mode.TOOLS):
276+
patched_create = patch_v2(
277+
client.messages.create,
278+
provider=Provider.YOUR_PROVIDER,
279+
mode=mode,
280+
)
281+
return Instructor(client=client, create=patched_create, mode=mode)
282+
```
283+
284+
4. **Export** (`instructor/v2/providers/your_provider/__init__.py`):
285+
286+
```python
287+
from . import handlers # noqa: F401
288+
from .client import from_your_provider
289+
__all__ = ["from_your_provider"]
290+
```
291+
292+
See `instructor/v2/providers/anthropic/` for a complete example.
293+
294+
## Migration from V1 to V2
295+
296+
**Key Differences**:
297+
298+
- Handler dispatch: Registry lookup (v2) vs direct calls (v1)
299+
- Mode validation: At patch time (v2) vs runtime (v1)
300+
- Exception context: Enhanced with attempt tracking (v2)
301+
302+
**Migration**: Add explicit `mode` parameter to factory functions:
303+
304+
```python
305+
# V1
306+
client = from_anthropic(anthropic_client)
307+
308+
# V2
309+
client = from_anthropic(anthropic_client, mode=Mode.TOOLS)
310+
```
311+
312+
V1 code continues to work during transition period.
313+
314+
## Best Practices
315+
316+
- **New Modes**: Define in `instructor.Mode` enum, create handler, register via decorator
317+
- **Error Handling**: Validate early, provide context, preserve exception chains
318+
- **Testing**: Test both success and failure paths, verify registry registration
319+
320+
## Module Organization
321+
322+
```text
323+
instructor/v2/
324+
├── __init__.py # V2 exports (ModeHandler, Protocols, Registry, Providers)
325+
├── README.md # This document
326+
├── core/
327+
│ ├── __init__.py # Core exports (Protocols, Registry)
328+
│ ├── decorators.py # @register_mode_handler decorator
329+
│ ├── exceptions.py # Exception classes & validation utilities
330+
│ ├── handler.py # ModeHandler abstract base class
331+
│ ├── patch.py # Patching mechanism
332+
│ ├── protocols.py # Protocol definitions (RequestHandler, etc.)
333+
│ ├── registry.py # Mode registry implementation
334+
│ └── retry.py # Retry logic (sync & async)
335+
└── providers/
336+
├── __init__.py # Provider exports
337+
└── anthropic/ # Anthropic provider implementation
338+
├── __init__.py # Provider exports
339+
├── client.py # from_anthropic factory function
340+
└── handlers.py # Handler implementations (TOOLS, JSON, etc.)
341+
```
342+
343+
## Module Exports
344+
345+
- `instructor.v2`: `ModeHandler`, `mode_registry`, `RequestHandler`, `ReaskHandler`, `ResponseParser`, `from_anthropic`
346+
- `instructor.v2.core`: Core types and registry
347+
- `instructor.v2.providers.anthropic`: `from_anthropic`

0 commit comments

Comments
 (0)