Skip to content

feat(v2): type the parse ParseResponse structure & grounding trees#108

Merged
yzld2002 merged 5 commits into
mainfrom
v2-parse-typed-structure
Jul 9, 2026
Merged

feat(v2): type the parse ParseResponse structure & grounding trees#108
yzld2002 merged 5 commits into
mainfrom
v2-parse-typed-structure

Conversation

@yzld2002

@yzld2002 yzld2002 commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

The V2 gateway now publishes a typed ParseResponse for POST /v2/parse (confirmed against the updated dev OpenAPI spec). Previously structure and grounding were modeled as loose Optional[object] (raw dicts). This PR models them as proper typed trees so callers get attribute access + autocomplete instead of dict indexing.

Follow-up to #106 (which covered service_tier + billing).

New models (types/v2/parse_response.py)

Field Before After
structure Optional[object] V2ParseStructureV2ParsePageV2ParseElement
grounding Optional[object] V2ParseGroundingV2ParseGroundingPageV2ParseGroundingElementV2ParseGroundingEntry

V2ParseElement and V2ParseGroundingElement are self-recursive (a table's children are its table_cells).

r = client.v2.parse(document=...)
page = r.structure.children[0]
page.width, page.status                      # 800, "ok"
cell = page.children[1].children[0]
cell.row, cell.col, cell.colspan             # typed ints
box = r.grounding.children[0].children[0].box  # [left, top, right, bottom]

Why it's safe / not breaking

  • client.v2 is unreleased — additive typed surface, no released consumers.
  • Deliberately permissive to avoid deserialization failures on spec drift: element type and page status are plain str (not Literal), spans/boxes are List[int], all non-required fields default, and BaseModel's extra="allow" keeps unknown keys. structure/grounding remain Optional.

Note on tolerance

The old test_parse_response_tolerates_loose_shape fed structure=[{"type":"text"}] (a bare list) to assert the maximally-loose object contract. That contract is what we're intentionally tightening, so the test was updated (test_parse_response_builds_from_dicts) to build from the real typed shape while still covering metadata/billing tolerance and unknown-key retention.

Test plan

  • TDD: typed-access + tolerance tests added first (red), then models implemented (green)
  • New tests: full nested structure/grounding typed access; unknown element type + extra keys tolerated
  • ✅ Full suite: 587 passed, 240 skipped
  • ruff check ., mypy . (77 files), and import landingai_ade all clean

🤖 Generated with Claude Code

The V2 gateway now publishes a typed ParseResponse for POST /v2/parse, so
model `structure` and `grounding` as proper trees instead of loose
`object`s:

- structure: V2ParseStructure -> V2ParsePage -> V2ParseElement
- grounding:  V2ParseGrounding -> V2ParseGroundingPage ->
              V2ParseGroundingElement -> V2ParseGroundingEntry

Callers get attribute access (page.width, cell.row/col, element.box,
part.span) with IDE autocomplete instead of raw dicts.

Kept deliberately permissive to avoid deserialization failures on drift:
element `type` and page `status` are plain `str` (not Literal), spans/boxes
are `List[int]`, and BaseModel's `extra="allow"` retains unknown keys.
`structure`/`grounding` stay Optional. Not breaking: client.v2 is
unreleased, and these are additive typed-surface changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings July 9, 2026 07:31
@yzld2002 yzld2002 added the breaking-change-approved Surface-lock override: a reviewed, intentional public-surface change label Jul 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a typed ParseResponse surface for client.v2.parse() by replacing the previously loose structure/grounding objects with explicit, recursive tree models that mirror the gateway schema, and updates tests/docs accordingly.

Changes:

  • Introduces typed V2ParseStructure/V2ParseGrounding model trees (document → page → element/entry) on V2ParseResponse.
  • Updates V2 parse tests to validate typed attribute access and tolerance for unknown element types/extra keys.
  • Exposes the new models from landingai_ade.types.v2 and documents them in api.md.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/landingai_ade/types/v2/parse_response.py Defines the new typed structure/grounding tree models and updates V2ParseResponse to use them.
src/landingai_ade/types/v2/__init__.py Re-exports the new parse/grounding model types from the v2 types package.
tests/test_v2_types.py Updates the parse response type test to build from dicts matching the new typed shape.
tests/api_resources/v2/test_parse.py Adds richer mocked parse responses and assertions for typed access and tolerance behavior.
api.md Documents the newly exported parse/grounding tree types and their relationships.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Copilot AI review requested due to automatic review settings July 9, 2026 07:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Addresses PR review: replace mutable `= []` defaults on the structure/
grounding list fields with `Field(default_factory=list)`, matching the
repo convention (e.g. Job.raw). No functional change — the construct_type
fast path already copies defaults per instance (verified) — but this is
the idiomatic, future-proof form.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings July 9, 2026 07:42
@yzld2002

yzld2002 commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review. All six comments point at the same thing — mutable [] defaults on the list fields — so addressed together in bcb1b72.

Verification first: the specific "shared list across instances" leak doesn't reproduce on this SDK's construct_type fast path — each constructed instance gets a fresh list and mutating one doesn't affect another (defaults are copied). So it wasn't an active bug.

Applied anyway because it's the right convention: switched all five non-optional list fields (V2ParsePage.children, V2ParseStructure.children, V2ParseGroundingElement.parts, V2ParseGroundingPage.children, V2ParseGrounding.children) to Field(default_factory=list), matching the existing repo pattern (Job.raw uses Field(default_factory=dict)) and the Field import is now present. The Optional[...] = None recursive children fields were already fine (no mutable default).

Tests + ruff/mypy green.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

The Field(default_factory=list) form trips the repo's strict pyright with
"Type of children is partially unknown" (bare list -> list[Unknown] vs the
List[TypedModel] annotation). Job.raw only gets away with default_factory
because its value type is `object`, which absorbs Unknown.

Reverting to `= []`: verified pyright-clean, and the shared-mutable-default
leak the review flagged does not reproduce on this SDK's construct_type
path (each instance gets a fresh, independent list).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings July 9, 2026 07:46
@yzld2002

yzld2002 commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Correction to my previous comment: I initially applied Field(default_factory=list), but it breaks the repo's strict pyright gate — a bare list factory infers list[Unknown], which pyright reports as "Type of children is partially unknown" against the List[TypedModel] annotation. (Job.raw gets away with default_factory=dict only because its value type is object, which absorbs Unknown; that doesn't hold for List[V2ParseElement] etc.)

So I've reverted to literal = [] defaults (ef3e256). This is:

  • pyright-clean (verified: 0 errors in the file),
  • safe on this SDK — I confirmed the shared-mutable-default leak does not reproduce on the construct_type fast path: two constructed instances get separate lists and mutating one doesn't affect the other.

Net: the review's underlying concern (cross-instance list sharing) is real in general but doesn't manifest here, and the suggested fix conflicts with the type-checker config — so the literal default is the correct form for this codebase. ruff/mypy/pyright and the full test suite are green.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread tests/api_resources/v2/test_parse.py
Addresses PR review: the tolerance test claimed extra keys are retained
but never asserted it. Now checks that `future_field` survives on the
deserialized page (extra="allow").

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings July 9, 2026 07:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
Comment thread src/landingai_ade/types/v2/parse_response.py
@yzld2002 yzld2002 merged commit 9b1ddca into main Jul 9, 2026
6 checks passed
@yzld2002 yzld2002 deleted the v2-parse-typed-structure branch July 9, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change-approved Surface-lock override: a reviewed, intentional public-surface change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants