Skip to content

fix(v2): resolve partial-streaming fields that arrive under a validation alias - #2668

Open
feiiiiii5 wants to merge 5 commits into
567-labs:mainfrom
feiiiiii5:fix/partial-streaming-alias-fields
Open

feiiiiii5 wants to merge 5 commits into
567-labs:mainfrom
feiiiiii5:fix/partial-streaming-alias-fields

Conversation

@feiiiiii5

@feiiiiii5 feiiiiii5 commented Sep 20, 2026 •

Copy link
Copy Markdown

Please use conventional commits to describe your changes. For example, feat: add new feature or fix: fix a bug. If you are unsure, leave the title as ... and AI will handle it.

AI-assisted development (investigation, patch and tests written by an agent, verified and reviewed by the account owner).

Describe your changes

Partial[Model].model_from_chunks() loses nested model instances for any field declared with a validation alias.

_build_partial_object() iterated the parsed JSON keys and used them twice: as the lookup key into model.model_fields, and as the key of the dict handed to model.model_construct(). model_fields is keyed by the Python attribute name, while the streamed JSON is keyed by the alias that model_json_schema() advertises, so both uses miss for an aliased field. The consequence is two-fold:

  • the field type is never found, so a complete nested object is stored as a raw dict instead of being validated;
  • the value is stored under the alias key, so model_construct() populates __dict__ with loc and the declared attribute location falls through to the "missing fields" branch and is replaced by an empty model.

Callers therefore get obj.location == {} plus a stray obj.loc dict, and obj.location.city raises AttributeError. Same for aliased list[Model] and Optional[Model] fields.

The patch resolves the parsed key to the attribute name once per field (canonical_field_name, covering alias, validation_alias and AliasChoices), keeps the raw key for the completeness tracker because the tracker works on the JSON text, and stores under the attribute name. Models without aliases take the key in model.model_fields fast path and behave exactly as before.

Repro (no API key needed, this is what the provider streaming handlers call):

class Location(BaseModel):
    city: str
    lat: float

class Aliased(BaseModel):
    answer: str
    location: Location = Field(alias="loc")

chunk = '{"answer": "hi", "loc": {"city": "NYC", "lat": 1.5}, "tail": '
print(list(Partial[Aliased].model_from_chunks(iter([chunk])))[-1].__dict__)
# before: {'answer': 'hi', 'loc': {'city': 'NYC', 'lat': 1.5}, 'location': Location(city='', lat=0.0)}
# after:  {'answer': 'hi', 'location': Location(city='NYC', lat=1.5)}

Issue ticket number and link

No existing issue or open PR covers this — I searched repo:567-labs/instructor for alias, partial alias and model_from_chunks across issues and PRs before opening. #2600 changed the same function for nullable list fields, which is the behaviour this extends to aliased fields.

Checklist before requesting a review

  • I have performed a self-review of my code
  • If it is a core feature, I have added thorough tests.
  • If it is a core feature, I have added documentation. — no user-facing documentation describes alias handling in partial streaming, so there is nothing to correct; say the word and I will add a note to docs/concepts/streaming.md.

How this was verified

details

Environment: python 3.11, PYTHONPATH pointing at the checked-out tree, instructor.__file__ confirmed to resolve inside the tree, -p no:opik to keep conda's unrelated plugin from aborting collection.

Base control (e12f8b49203b0c1f253d27c1e709d0a09b9fc5a8, instructor/v2/dsl/partial.py restored from that commit, git status for the file empty):

$ python -m pytest tests/dsl/test_partial_aliases.py -p no:opik -q
2 failed, 1 passed in 0.37s

The passing case is the deliberate non-alias control, which shows the harness measures the real path rather than failing for an unrelated reason.

Same file, same command, with the patch applied:

$ python -m pytest tests/dsl/test_partial_aliases.py -p no:opik -q
3 passed in 0.25s

No regression in the surrounding suites:

$ python -m pytest tests/dsl tests/coverage/test_dsl_partial_coverage.py tests/core/test_schema.py -p no:opik -q
167 passed, 2 skipped

Static checks, both clean:

$ python -m ruff check instructor/v2/dsl/partial.py tests/dsl/test_partial_aliases.py   # All checks passed!
$ python -m ruff format --check instructor/v2/dsl/partial.py tests/dsl/test_partial_aliases.py   # 2 files already formatted
$ python -m ty check instructor/v2/dsl/partial.py   # All checks passed!

Per CLAUDE.md a behavior change needs a [Unreleased] entry in CHANGELOG.md; that entry links the PR number, so it follows in a second commit as soon as this PR is open.

Known limitations

details
  • validation_alias=AliasPath(...) is not resolved in partial snapshots: _alias_keys() only collects string aliases and AliasChoices, and an alias path would need its nested value lifted while the object is still incomplete. Covered here only as a documented scope boundary; the reproduction lives in the review thread. String aliases (the plain alias= case that dropped fields before this PR) are covered.

The streamed JSON is keyed by the alias model_json_schema() advertises while model_fields is keyed by the attribute name, so _build_partial_object looked up aliased fields by the wrong name and stored the raw payload under the alias key. Nested models behind an alias therefore reached the caller as plain dicts, and model_construct() left the declared attribute unset.
@feiiiiii5

Copy link
Copy Markdown
Author

Hi — anything you'd like changed before this can go in? Both GitHub Actions workflows on the fork PR are parked at action_required, so the suite has not actually run yet; locally pytest tests/ and ruff are clean on the branch, and it is rebased on the current main (head 2b02833).

The change resolves partial-streaming fields that arrive under a validation alias rather than dropping them, and the regression test streams a chunk that carries only the alias and asserts the resolved field reaches the model. Happy to switch to a different shape if you would rather it error than resolve.

@feiiiiii5

Copy link
Copy Markdown
Author

Correction to the line about a clean local pytest/ruff run: drop it. I did not run them for this PR and I cannot point to a recorded result, so that part is unverified.

What I can state is the rest: both workflows are parked at action_required, and the branch sits on the current main at 2b02833. If you would rather not approve the workflows, say so and I will run the suite here and report the output.

jxnl added a commit that referenced this pull request Oct 10, 2026
## Current Candidate: Jev and OpenAI Decisions

Exact head: `9e7a61a9d85aacd812ec8e77a68b9d2adaecef66`. OpenAI Decisions
support and the same sync/async provider/configuration contract family
as OpenRouter and TypeSafe are included. Shared suite: **294 passed, 1
existing skip**. Decision source has **373/373 statements covered**;
source unchanged since coverage measurement. Full source typing, test
typing, and normal commit hooks passed. Six opt-in live provider cases
were skipped without explicit opt-in; no billed API calls. Native OpenAI
image input and live OpenAI compatibility remain unverified.

Documentation: complete MkDocs build passed (13.17s; existing
navigation/docstring warnings), primary example model definitions
executed without API calls, guide linked in navigation, and OpenAI entry
included in 1.18 release notes. Guide documents all three routes, raw
wire differences, text-only support and shared/live test configuration.

Fresh exact-head CI and required independent approval remain mandatory.
Old-head auto-merge was disabled before updates. Previous-head checks
below are historical evidence, not current proof. No merge or
publication in this continuation.

[Implementation and earlier validation
details](#2708 (comment)).

## Prior Integration Evidence

## Summary

Curated Instructor 1.18 integration from main
`e12f8b49203b0c1f253d27c1e709d0a09b9fc5a8`, current candidate
`b76939ea652202842ae26aec4663ef52d13356d7`. Ready for independent
review; this is not approval to merge or publish.

- Include the reviewed typed Jev/TypeSafe and OpenRouter decision-mode
feature and 1.18.0 metadata from #2707.
- Consolidate hook dispatch (#2634), custom batch IDs (#2691), UTF-8
batch I/O (#2667), cache round-trips (#2688), citation validation
(#2699), GenAI retry history (#2632), custom-type compatibility (#2629),
and example corrections (#2697).
- Revise IPv6 SSRF filtering (#2673) and symmetric streaming JSON bounds
(#2638); preserve incremental streaming and public network policy.
- Unify declared parallel tool names and reject unknown calls across
providers (#2675/#2698), including actual retry boundaries and native
xAI transport. Preserve the public generator contract and VertexAI
class-name wire exception.
- Isolate request histories in public retry/reask helpers (#2646) and
fix incomplete Responses validation/cache acceptance (issue #2705).
- Repair Python 3.9 generic-alias traversal without weakening
async-validator rejection; remove redundant helper wrappers, obsolete
version-specific test hacks and a duplicate negative test.

The selected patches were revised where needed, not merged blindly. New
transport contracts use real SDKs and loopback HTTP/gRPC, not new mocked
provider clients. Existing legacy mocks are not claimed as live provider
evidence.

## Validation

Current candidate: `b76939ea652202842ae26aec4663ef52d13356d7`. Earlier
local results below are for head
`a3137a76159611c080ebcc0c36ccf0383901e2f2` unless a different head is
named.

- Normal commit hooks: Ruff lint/format and source typing passed.
- Whole-test-tree typing passed with the SDK-complete environment.
- Read-only final runtime review identified a Python 3.9 nested-generic
class-check defect; fixed and independently reverified locally. This is
not GitHub approval.
- Minimum-version OpenAI loopback retry/cache/parallel contracts: 36
passed. Other SDKs are absent from this temporary core-only environment;
their contracts passed in the full SDK-complete Python 3.13 suite, not
in the minimum environment.
- Focused Python 3.9/Pydantic 2.8.2 compatibility: 174 passed, 6 version
skips.
- Isolated wheel built from sdist: Python 3.9.25/OpenAI 2.0.0/Pydantic
2.8.0 contracts and credential-free CLI help passed.
- Isolated wheel built from sdist: Python 3.13.11/OpenAI 3.3.0/Pydantic
2.13.5 contracts and credential-free CLI help passed.
- Release metadata/changelog extraction validates 1.18.0.
- All **29 runnable remote checks passed** at
`a3137a76159611c080ebcc0c36ccf0383901e2f2`; publication was
intentionally skipped. Required independent review remains unsatisfied.
- Cursor Bugbot found that parallel parsing of an empty/prose response
could escape the retry boundary as an unhandled exception. Head
`9bcef9849fa609040c65fec30f1d3869e64d415e` adds `ResponseParsingError`
guards and a missing-tool-calls regression test. Its full offline suite
passed **3,963 tests**, but the zero-missing-statements gate failed on
the untested empty-choices branch. Head
`ba0db64bad4d9112fdcaef082bfd9a382a25e57c` adds that SDK-validated
regression test; the complete focused parallel suite passed **54
tests**, plus Ruff lint/format and focused test typing.
- At `ba0db64bad4d9112fdcaef082bfd9a382a25e57c`, **all 29 runnable CI
checks passed** and publication was intentionally skipped. Full offline
coverage ran **3,964 passed, 200 skipped, 859 deselected**, with **zero
missing statements** and 4 missing branches. No threshold was reduced.
- The current head changes only the decisions example and changelog
accuracy: its primary example constructs under Python 3.9, and a full
`mkdocs build --clean` passed. At
`b76939ea652202842ae26aec4663ef52d13356d7`, **all 29 runnable CI checks
passed**, publication was intentionally skipped, and full offline
coverage reported **3,964 passed, 200 skipped, 859 deselected**, **zero
missing statements**, and 4 missing branches. Independent maintainer
approval remains outstanding.
- Full credential-free offline suite: **3,962 passed, 200 skipped, 859
deselected**, with runtime/unraisable/thread warnings treated as errors.
- Combined statement/branch coverage: **99.9700%**, **0 missing
statements**, 4 missing branches; unchanged >=99% and
zero-missing-statement gates passed. Existing version/optional/runtime
exceptions remain disclosed; no threshold reductions.
- Focused decision-mode contracts at the same head: **240 passed, 1
skipped** with credentials removed and localhost transport enabled. The
initial sandbox-only run could not bind loopback sockets; the unchanged
suite passed when loopback binding was permitted.
- Opt-in live OpenRouter Jev model (`typesafe/jev-1.13`): **sync and
async contracts passed** using the configured local key. No credential
or response payload was added to this PR. Direct TypeSafe/Jev live calls
remain unverified because `TYPESAFE_API_KEY` is unavailable.
- Full `mkdocs build --clean` passed at the same head, writing only to a
temporary site directory. It reported existing nav omissions and two
griffe annotation warnings; no documentation build errors.

No other live provider calls were made in this integration validation.
Exhaustive repository security certification is not claimed. The Bugbot
finding was addressed and its review thread resolved. Current-head CI
passed; required independent maintainer approval remains the merge gate.
No publication, tag, deployment, main merge or issue closure.

## Security and Compatibility

- Reject the entire local-use NAT64 `64:ff9b:1::/48` prefix. Its
translation layout is network-specific; assuming a fixed embedded
destination is unsafe for this fetcher's public-network-only policy
([RFC 8215, section
5](https://www.rfc-editor.org/rfc/rfc8215.html#section-5)). Known
well-known NAT64/mapped/compatible/6to4/Teredo destinations are checked
as well as their IPv6 wrapper.
- Streaming input limits are 1,048,576 characters and 128 nested
containers, sync/async. Errors remain `ValueError`; complete objects can
still emit before EOF.
- Unknown parallel calls now raise `ResponseParsingError` inside retry
rather than silently losing results. Nonstream validation is eager while
the returned public generator type is preserved.
- Incomplete Responses raise `IncompleteOutputException` before defaults
or caches can turn partial output into an accepted answer. SDK
metadata/reason remain attached.
- Citation context accepts strings or lists/tuples of strings; malformed
supplied sources fail closed. Missing context preserves existing
behavior.
- Decision mode is opt-in and currently does not support retries,
caching, streaming or completion hooks. OpenRouter Jev sync/async
compatibility passed live; direct TypeSafe/Jev compatibility is still
supported by shared offline contracts only.

## Overlaps and Deferrals

Reviewed the current 78-PR/28-issue queue. Compact selected changes
received detailed source review; large providers/features/architecture
received scope triage, not blanket approval. Absent CI is not passing.
The table below records every source head inspected; these heads are not
independently certified merge-ready.

## Every Open PR

| PR / Inspected Head | Decision |
| --- | --- |
| [#2707](#2707)
`a2f0b309464b70b8936054466e6a365d16c9cb39` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2704](#2704)
`5061c6866f2c94f46eedd4336753e9db8215fd9b` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2703](#2703)
`248effa5c97e5c046685d7c6b144c4cbc7863702` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2701](#2701)
`82a083207fe45993e339d74a96c5d2b0cba634fc` | Blocked: recursive
described $ref causes infinite recursion; mappings change meaning. |
| [#2700](#2700)
`79aa81e1dec364422dcac7401e67c150a547250c` | Deferred: buffers valid
streaming output until EOF, breaking incremental behavior. |
| [#2699](#2699)
`27dd66315794394a9c471c6a75c2ad4b538abae6` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2698](#2698)
`59316edcbe5c1f3cc094d70f042a9d43a26a8db5` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2697](#2697)
`692433fa8e04c21274232d83d042a27f37113924` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2693](#2693)
`bfd56e7dda4d3444fd4d866f1c7399a5bee58b55` | Deferred: reconcile
strict-schema required/defaulted fields and mapping policy together. |
| [#2692](#2692)
`89f9db983865175e92a7096615ce2bcc454bfb07` | Separate dependency
maintenance: compatibility and supply-chain review required. |
| [#2691](#2691)
`81f28eb416c206c1b8ae702705e66007ac5e107d` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2690](#2690)
`7c829eb06297d07bcd1ff957c481d8c042ef64da` | Deferred editorial: patch
still leaves incorrect provider spelling/capitalization. |
| [#2689](#2689)
`9688857ccba01dbebc4d3f5db0c1d158780c6c73` | Deferred docs/examples:
maintenance/product choice; no correctness-release necessity. |
| [#2688](#2688)
`f47161c87b872e13e54aecb9aed24e1a131a938b` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2686](#2686)
`545232b6ff01f78fb938e84daeec4e876dfb45da` | Next compact batch:
promising fixes, require exact-head runtime and combined validation. |
| [#2685](#2685)
`7782155fc19e435173fa32ab7d5f78815bf99111` | Blocked: rejects native
Converse tool histories and loses multiple system blocks. |
| [#2683](#2683)
`3b09a98e768bfd2955164fe320cf0994a6f4b2a5` | Next compact batch:
promising fixes, require exact-head runtime and combined validation. |
| [#2680](#2680)
`7f70d2099a4b598f67a1b09e00ad7bed15cb832b` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2678](#2678)
`0bed63816628aec034f5fc965b8d719d48bb0b8e` | Deferred: broader
mapping-field response model construction. |
| [#2677](#2677)
`2c0276aea1b25f061b3949b9883bd482ddd183cc` | Next compact batch:
promising fixes, require exact-head runtime and combined validation. |
| [#2675](#2675)
`e69a43d3cd7b8a3a8df758c946af510ef036e917` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2673](#2673)
`06d34c3039faf530296960d24d50e8df8f9d9a6b` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2672](#2672)
`4df16706ab588da537f76edec57b33ae9ff6141b` | Deferred: Mistral batch
needs failure/error-file retrieval and provider evidence. |
| [#2671](#2671)
`c3d95dccd28e803fa08cd89c87f5692ea044701a` | Deferred: global
StrictUndefined is a deliberate compatibility change. |
| [#2670](#2670)
`9763946ad1795ad6428daf277df06a07ee3c5dda` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2668](#2668)
`2b028330d359994d3cd6c7ee6f2274bb950b2310` | Blocked:
AliasChoices/AliasPath are unhashable in proposed alias set. |
| [#2667](#2667)
`137b253c9e8b50602c39466c135b2e3c7400e3e0` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2666](#2666)
`f2a1c390cd605c9928029d937c860b22456d26f2` | Next compact batch:
promising fixes, require exact-head runtime and combined validation. |
| [#2664](#2664)
`77f427ad196bed5c3bd6d61b3bd0855285d8e8e1` | Split first: useful Mistral
usage-only fix, but Google empty candidates can erase safety refusal. |
| [#2662](#2662)
`4c6e06613fc30a3ef6d29374e8df92bfb2a4122d` | Next compact batch:
promising fixes, require exact-head runtime and combined validation. |
| [#2660](#2660)
`1207effdd29b1aa1da9e0a283eda5551835f1a01` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2657](#2657)
`8f619b3ab20d5cc6914b42c38bba80f6aa8c4585` | Next compact batch:
promising fixes, require exact-head runtime and combined validation. |
| [#2656](#2656)
`01c7bee784497eb87c7edab13ff1c64095f2c93c` | Deferred docs/examples:
maintenance/product choice; no correctness-release necessity. |
| [#2655](#2655)
`f4ce1943f2d666c465728062bc342d8934c55e66` | Next compact batch:
promising fixes, require exact-head runtime and combined validation. |
| [#2652](#2652)
`4bd39745d0ba809e293973697aa7a0be8ab64c81` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2651](#2651)
`68786153500ce306f5bd65bb051aedd080e48a73` | Next compact batch:
promising fixes, require exact-head runtime and combined validation. |
| [#2649](#2649)
`f704f3530caef4891214a00dac7abd8496e92e96` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2647](#2647)
`9bfaa3b75f83568063fa38dbd84d28a0a0848c0d` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2646](#2646)
`a6dce755f054b1b2088faf8fffaea7dbfac05db7` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2644](#2644)
`eda57883460ed8329775e9de4ede4a87b2786c67` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2643](#2643)
`0fd74545d374afb249d01667a280115fa75b1156` | Deferred: main rejects
async validators; set traversal is not a release necessity. |
| [#2642](#2642)
`f208aec71909a7b27358bbdbfc2638a574585776` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2641](#2641)
`2506e18dd4ed4e12bcc25069a4ce449138cf03b1` | Deferred: reconcile
strict-schema required/defaulted fields and mapping policy together. |
| [#2640](#2640)
`ca1fe9e902abdb5a1aea9eab04afdb133ae39c83` | Separate maintainer
metadata hygiene. |
| [#2639](#2639)
`e91095777cf4d031e7414bcaa427ab989c42f96e` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2638](#2638)
`54ee418cad55aa93d9b6d1623a2e1b5ba066796e` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2637](#2637)
`8b580fe0f2503dfa758a2d5be5957db81794c6ef` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2636](#2636)
`3152a697f423b32bbe25af8021a948c35d8af41c` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2634](#2634)
`aab02eaba1215a0f1e6ccf2139e88b1cc5539a8d` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2633](#2633)
`2f337b3148717a76209616b72052ff1d4fb95709` | Deferred: timeout contract
needs regression proof; current CI red. |
| [#2632](#2632)
`ad3e32283f0ae51b3f06f5e06dc0415be282c5f5` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2630](#2630)
`e5515c629eb4a94fab9953a1647de0ee66797132` | Overlap: selected broader
alternatives; older variants regress Python 3.9 or metadata. |
| [#2629](#2629)
`438debe231c3a49c9168b478ec82a82055983f47` | Integrate/revise: reviewed
compact fixes; preserve compatibility and repair tests. |
| [#2605](#2605)
`7e7d33a821b1b1593257a820ab52e656317405a8` | Blocked: cache values
inherit and retain weak keys; GC test does not prove collection. |
| [#2604](#2604)
`ce322502e3f4ba1f0ff62d5524965fec5233806a` | Deferred: markdown
sanitization needs adversarial regression oracle. |
| [#2602](#2602)
`a1229322d2b865d0224a2a94d222375eae4396bc` | Deferred providers/auth:
scope-triaged, need ownership, SDK/wire contracts and live evidence. |
| [#2598](#2598)
`6b153c8ba75e25461c43cd05c9b52f67ae3cb830` | Deferred providers/auth:
scope-triaged, need ownership, SDK/wire contracts and live evidence. |
| [#2574](#2574)
`0df33ba1111a6dc9da6788429bab9d4106bef2ae` | Separate dependency
maintenance: compatibility and supply-chain review required. |
| [#2573](#2573)
`2485aa6f4b9d7b7cb445214547968e1a7e61b564` | Separate dependency
maintenance: compatibility and supply-chain review required. |
| [#2572](#2572)
`c152a36b244b5e2618bd5e06e13ee1ce1876fa65` | Separate dependency
maintenance: compatibility and supply-chain review required. |
| [#2571](#2571)
`db4abe9654e386acbe9e64585b56bda419b0daa7` | Separate dependency
maintenance: compatibility and supply-chain review required. |
| [#2570](#2570)
`47caa02a3a924ee4f889e6cba9572da58ff40398` | Separate dependency
maintenance: compatibility and supply-chain review required. |
| [#2531](#2531)
`4f0a66465f96f08e256cdc3f0608e7578cf2ad9d` | Deferred docs/examples:
maintenance/product choice; no correctness-release necessity. |
| [#2468](#2468)
`6e68201124bca8957d4fe5ca42bd4ef5203e77c6` | Deferred docs/examples:
maintenance/product choice; no correctness-release necessity. |
| [#2435](#2435)
`bb31451d31df1220509538fbf4e4a01c0d7a79ca` | Deferred providers/auth:
scope-triaged, need ownership, SDK/wire contracts and live evidence. |
| [#2423](#2423)
`3b4389f70fdefd30f0be65219d27e981ff4cd98a` | Deferred providers/auth:
scope-triaged, need ownership, SDK/wire contracts and live evidence. |
| [#2409](#2409)
`87a9bf8a3a1579b0d394e92226cb315440c79427` | Deferred providers/auth:
scope-triaged, need ownership, SDK/wire contracts and live evidence. |
| [#2405](#2405)
`82e90e9bea0da26e20d2d37add94b9cc29b42af7` | Deferred docs/examples:
maintenance/product choice; no correctness-release necessity. |
| [#2394](#2394)
`bc55f4f639ad352fd9314b9f6f477971b6ee4e83` | Deferred product/security
design: routing and injection-defense guarantees need evaluation/threat
model. |
| [#2384](#2384)
`b7371363d705acb8c1cd344efc293cd42369ea69` | Deferred providers/auth:
scope-triaged, need ownership, SDK/wire contracts and live evidence. |
| [#2357](#2357)
`182609c2e4789ebb40430ecba3407c27b50ebd52` | Deferred architecture
stack: separate migration and release; tests/docs target parent branch.
|
| [#2356](#2356)
`913a6053a0047b2c97c07768f9374c8a5243c9a9` | Deferred architecture
stack: separate migration and release; tests/docs target parent branch.
|
| [#2355](#2355)
`b3c44096ecd5f92619743bf3182ce21a3aef6e1d` | Deferred architecture
stack: separate migration and release; tests/docs target parent branch.
|
| [#2322](#2322)
`222c4f12430eea028f5895a68c96f15cbcd7513e` | Deferred providers/auth:
scope-triaged, need ownership, SDK/wire contracts and live evidence. |
| [#2321](#2321)
`b70f25defed4c8e0927b836fdb9ea01a9b5c1d9f` | Deferred product/security
design: routing and injection-defense guarantees need evaluation/threat
model. |
| [#2311](#2311)
`d5df24eb3a973a0462c04988a69e05baddd3ad79` | Deferred docs/examples:
maintenance/product choice; no correctness-release necessity. |
| [#2306](#2306)
`a960f6462a22a559f05ae14e9159c84e71bae19f` | Deferred providers/auth:
scope-triaged, need ownership, SDK/wire contracts and live evidence. |
| [#2283](#2283)
`5bbaa6217e83b5d69d862fd12470aef57a77c5ca` | Deferred providers/auth:
scope-triaged, need ownership, SDK/wire contracts and live evidence. |


## All Open Issues

- [#2260](#2260): #2306
provider ownership needed.
- [#2272](#2272): no
matching PR; benefits unverified.
- [#2316](#2316): #2321
needs threat model.
- [#2383](#2383): #2384
broader Azure provider deferred.
- [#2393](#2393): #2394
routing product choice.
- [#2403](#2403): memory
feature, related #2405 but not duplicate of #2404.
- [#2404](#2404): #2405
cookbook product choice deferred.
- [#2408](#2408): #2409
authentication deferred.
- [#2603](#2603): #2605
does not prove leak fixed; sustained leak not reproduced.
- [#2613](#2613): #2629
selected, preserve supported schemas/scalars.
- [#2631](#2631): #2632
selected; #2637/#2639 overlapping.
- [#2645](#2645): #2646
selected; #2647/#2652/#2704 overlaps.
- [#2650](#2650): #2651
next batch.
- [#2654](#2654): #2655
next batch.
- [#2659](#2659): #2673
selected; #2660/#2680 alternatives.
- [#2661](#2661): #2662
next batch.
- [#2665](#2665): #2666
next batch.
- [#2669](#2669): #2671
deliberate policy deferred.
- [#2674](#2674): #2675
selected and unified.
- [#2676](#2676): #2677
next batch.
- [#2681](#2681): #2686
next batch; forced default remains limitation.
- [#2682](#2682): #2683
next batch.
- [#2684](#2684): #2685
blocked on native history.
- [#2687](#2687): #2688
selected.
- [#2694](#2694): #2700
policy deferred.
- [#2695](#2695): #2701
blocked.
- [#2696](#2696): #2698
selected; #2703 duplicate.
- [#2705](#2705): new
coordinator fix.


No superseded item is closed yet: consolidation must receive review
first. Use this PR rather than merging overlapping patches separately.
Strict-schema #2701 is blocked by recursive $ref overflow and mapping
semantics; #2700's EOF buffering is excluded because it breaks
streaming. The deferred dependency/provider/architecture groups require
separate validation and ownership.
@lj1166

lj1166 commented Nov 2, 2026

Copy link
Copy Markdown

The string-alias coverage here fixes the plain alias= case, but AliasPath remains unresolved: _alias_keys() discards non-string path aliases, so canonical_field_name() never maps the parsed outer key.

Minimal reproduction on current main (Python 3.12, Pydantic 2.12.5):

from pydantic import AliasPath, BaseModel, Field
from instructor.dsl.partial import Partial

class Inner(BaseModel):
    value: int

class Outer(BaseModel):
    payload: Inner = Field(validation_alias=AliasPath("container", "payload"))

snapshot = list(Partial[Outer].model_from_chunks(['{"container":{"payload":{"value":1}']))[-1]
assert isinstance(snapshot.payload, Inner)  # current: dict

Outer.model_validate({"container": {"payload": {"value": 1}}}) produces Inner(value=1), so the streaming snapshot diverges before the root closes. Could the PR either add an AliasPath regression or explicitly scope it out? The current helper's isinstance(key, str) guard means the test would remain red after this patch.

@feiiiiii5

Copy link
Copy Markdown
Author

Good catch — that is a real limitation, but it is a different mechanism from what this PR fixes, not a missing branch in the alias-key set.

_alias_keys() only collects string aliases and AliasChoices entries. An AliasPath maps a nested input ({"container": {"payload": ...}}) to a field, so supporting it means lifting the value along the path while the surrounding object is still incomplete — the snapshot builder currently validates the top-level dict, where there is no payload key to resolve. Doing that properly is its own change, and I would rather not grow this PR after review without a maintainer asking for it.

I have added an explicit "Known limitations" note to the PR description pointing at AliasPath (with the reproduction from this thread) so the scope is documented rather than implied. If you or a maintainer want AliasPath folded in here, say so and I will add the path-lifting plus your regression; otherwise it belongs in a follow-up issue.

@lj1166

lj1166 commented Nov 3, 2026

Copy link
Copy Markdown

Thanks for clarifying the boundary. I split the AliasPath behavior into #2872 and opened #2873 with sync, async, and indexed-path regression coverage, so #2668 can stay focused on its string-alias fix.

A validation alias may be an AliasChoices or AliasPath, and _alias_keys collected them in a set literal, raising TypeError for unhashable objects before canonical_field_name could map the parsed key. Collect into a list/tuple, keep only strings, expand AliasChoices string choices, and ignore AliasPath (documented limitation; nested path lifting is a separate change). Add partial-streaming regressions for AliasChoices keys and AliasPath not crashing.
@feiiiiii5

Copy link
Copy Markdown
Author

Thanks for splitting the AliasPath behavior out — #2668 stays scoped to string aliases (and now resolves AliasChoices string choices without tripping over unhashable alias objects; AliasPath is documented as a known limitation and no longer crashes). I'll leave #2872/#2873 to the reviewers you mentioned.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants