feat: schema complexity analyzer + automatic mode selection - #2394
feat: schema complexity analyzer + automatic mode selection#2394mimran-khan wants to merge 1 commit into
Conversation
|
Reviewed for merge readiness. I would keep this open for #2393, but not merge it yet: it adds new public API ( |
Introduce two complementary utilities that help users get better extraction results on the first attempt: 1. `analyze_schema(response_model)` inspects a Pydantic model's JSON schema and reports complexity metrics (depth, field count, recursion, large enums) along with actionable findings and a 0-100 complexity score. 2. `select_mode(response_model, provider)` picks the optimal extraction mode for a given provider by cross-referencing schema complexity with provider capabilities. Handles recursive schemas, deep nesting, and high-complexity patterns by routing to the mode most likely to succeed. Both are exposed at `instructor.analyze_schema` and `instructor.select_mode` for convenient top-level access.
8972db1 to
bc55f4f
Compare
|
Thanks for the feedback. I've addressed both points:
Branch is force-pushed so CI should pick it up fresh. |
## Summary - accumulate declared, nested, and unknown numeric OpenAI/Anthropic usage fields across retries while preserving non-numeric metadata - add corrective feedback when a Responses API retry receives no tool call - preserve raw iterable type hints through sync and async v2 parallel-tool wrappers - strengthen API-key-free coverage for current and future SDK usage counters ## Consolidated and superseded items - closes #2493 - consolidates contributor work from #2498, #2500, and #2501 with original commit authorship preserved - supersedes #2497 because it drops unknown `model_extra` counters - supersedes #2499 because its hand-maintained provider field lists would drift as SDKs evolve ## Validation - focused changed-surface suite: `110 passed` - broad offline v2/coverage suite: `2368 passed, 91 skipped, 73 deselected` - Ruff check and format check: passed - scoped `ty check`: passed - `uv lock --check`: passed - pre-commit hooks and `git diff --check`: passed The 73 deselected tests require live provider credentials. An unfiltered local run confirmed its 22 failures were provider network connections in the restricted environment; GitHub provider jobs remain the authoritative validation for those paths. ## Intentionally skipped - provider additions or expansions: #2436, #2435, #2423, #2409, #2384, #2322, #2306, #2298, #2283, #2168, #2086; issues #2408, #2383, #2365, #2260, #2084, #2076 - broad architecture, product, security, or streaming decisions: #2394, #2392, #2357, #2356, #2355, #2351, #2321, #2307, #2287, #2263; issues #2479, #2403, #2393, #2391, #2316, #2272, #2056 - dependency batch: #2433 - nontrivial examples and editorial/resource additions: #2468, #2405, #2401, #2354, #2346, #2311, #2305; issue #2404 These remain open because they need dedicated product, architecture, provider, security, dependency, or editorial review and are not required for the `1.15.5` patch release. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes retry usage totals and reask message content on failure paths; scope is limited and heavily covered by tests, with no auth or data-store changes. > > **Overview** > Bundles three v2 retry and wrapper fixes for a patch release. > > **Retry usage accounting** replaces hand-maintained token field sums with generic `_accumulate_models` on Pydantic usage objects. Numeric fields (including nested models and `model_extra` counters) add across retries; booleans and other non-numeric metadata are not treated as billable. OpenAI and Anthropic paths share this logic. > > **OpenAI Responses reask** appends a user correction when `RESPONSES_TOOLS` validation fails but the output has no tool calls (e.g. reasoning-only), so retries include feedback instead of repeating the same request. > > **Parallel tools** in `patch_v2` skips `prepare_response_model` and does not replace `response_model` with the handler’s prepared wrapper for parallel modes, keeping raw `Iterable[...]` hints so schemas and parsed results include every member type. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit bbddca1. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
|
1.16 triage update: keeping this open, not merging it in the maintenance release. The analyzer may be separable, but |
What this does
I noticed that picking the right mode is a common friction point, especially for users with complex nested schemas. This PR adds two utilities that solve this at the library level:
analyze_schema(response_model)- inspects a Pydantic model before any API call and reports:select_mode(response_model, provider)- picks the optimal extraction mode by cross-referencing schema complexity against provider capabilities:Why
I ran into this myself while testing extraction on deeply nested models. The retry loop burned tokens because the schema was too complex for TOOLS mode, but there was no guidance about that. Checking the issues list, I found similar confusion across multiple discussions.
These utilities are pure functions with no side effects, no network calls, and zero additional dependencies. They are lazy-loaded so they don't affect import performance.
Files changed
instructor/v2/core/schema_analyzer.py- the complexity analysis engineinstructor/v2/core/auto_mode.py- mode selection logic using provider specsinstructor/__init__.py- top-level exports (analyze_schema, SchemaAnalysis, select_mode)instructor/v2/core/__init__.py- v2 core exportstests/test_schema_analyzer.py- 28 tests for the analyzertests/test_auto_mode.py- 31 tests for mode selectionUsage
Test results
All 59 new tests pass. Broader suite (1537 tests) has no regressions.
Closes #2393
Checklist before requesting a review