Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions docs/v2-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,29 +112,29 @@ fields the model could not extract — the extraction is partial), `warnings`
`output_extraction_chars` char counts moved from `billing` onto `metadata`
upstream; both are retained on `V2ExtractBilling` for backward compatibility.

### Extraction options (`strict`, and the unwired `grounding`)
### Extraction options (`strict`)

`/v2/extract` and `/v2/extract/jobs` take a nested `options` object. Unlike parse,
extract does **not** expose `options` as a keyword — the only nested option the SDK
reaches is `strict`, through a hand-written top-level shorthand that folds into
`options.strict`.

The snapshot added a second option, `options.grounding` (default `true`; set it
`false` to skip the grounding stage, which makes every `extraction_metadata` leaf
carry `ranges: null` and returns faster — marked Preview upstream). **It is
deliberately not wired.** It is nested, not a top-level `requestBody` property, so
surfacing it would mean adding a third hand-written alias next to `password` and
`strict`. CONTRIBUTING.md pins those two as a cross-SDK contract shared with
`ade-typescript`, and says in as many words that exposing `options` on extract
"becomes a real tie-break and needs a rule here first" — so adding one is a
maintainer's call, not a spec-sync one. Callers who need it today can send
`extra_body={"options": {"grounding": False}}`; `extra_body` merges at the top level
only, so that replaces the whole `options` object and `strict` has to be repeated
inside it rather than passed as `strict=`.

Nothing about the **response** changed: `grounding=false` only nulls out `ranges`
inside `extraction_metadata`, which `V2ExtractResult` already models as an untyped
`Dict[str, object]`.
`strict` is once again the **only** member of that object. A previous snapshot had
briefly added a second, `options.grounding` (a Preview flag that skipped the
grounding stage so every `extraction_metadata` leaf came back with `ranges: null`).
It was deliberately never wired — it is nested, not a top-level `requestBody`
property, so surfacing it would have meant a third hand-written alias next to
`password` and `strict`, which CONTRIBUTING.md pins as a maintainer's call rather
than a spec-sync one. This snapshot drops `grounding` from the spec altogether, so
there is nothing left to decide and no SDK surface to retire.

One consequence worth knowing when re-running older scratch scripts: the `options`
schema is `additionalProperties: false`, so the `extra_body={"options": {"grounding":
False}}` escape hatch that reached the flag against the previous snapshot is now an
unknown key and is rejected by the gateway. Drop it; pass `strict=` as usual.
Comment thread
tian-lan-landing marked this conversation as resolved.

Nothing about the **response** changed — `extraction_metadata` is unaffected, and
`V2ExtractResult` already models it as an untyped `Dict[str, object]`.

The async `extract_jobs.create` also accepts `output_save_url` (async jobs only):
when set, the finished result is delivered to that URL and the completed job
Expand Down
15 changes: 4 additions & 11 deletions specs/_generated/v2_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,11 +559,6 @@ class V2ExtractOptions(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
grounding: Optional[bool] = Field(
True,
description='When ``true`` (default), the grounding stage runs and each extracted leaf carries ``ranges`` into the source Markdown where a reference was found (``ranges`` is ``null`` for a value that could not be located). When ``false``, the grounding stage is skipped: every ``extraction_metadata`` leaf has ``ranges: null`` and the request completes faster. Preview.',
title='Grounding',
)
strict: Optional[bool] = Field(
False,
description='When ``true``, a schema containing fields the model cannot extract fails with a validation error — HTTP 422 on the sync route, or a failed job (``status: "failed"``) on the async ``/jobs`` route. When ``false`` (default), unsupported fields are skipped and extraction continues.',
Expand Down Expand Up @@ -1898,8 +1893,7 @@ class V2ExtractPostRequest(BaseModel):
title='Model',
)
options: Optional[V2ExtractOptions] = Field(
None,
description='Extraction options (``strict``, ``grounding``). Omit for defaults.',
None, description='Extraction options (``strict``). Omit for defaults.'
)
schema_: dict[str, Any] = Field(
...,
Expand Down Expand Up @@ -1932,7 +1926,7 @@ class V2ExtractPostRequest1(BaseModel):
)
options: Optional[V2ExtractOptions] = Field(
None,
description='Extraction options (``strict``, ``grounding``). Omit for defaults. JSON-serialized string in form data.',
description='Extraction options (``strict``). Omit for defaults. JSON-serialized string in form data.',
)
schema_: dict[str, Any] = Field(
...,
Expand Down Expand Up @@ -2053,8 +2047,7 @@ class V2ExtractJobsPostRequest(BaseModel):
title='Model',
)
options: Optional[V2ExtractOptions] = Field(
None,
description='Extraction options (``strict``, ``grounding``). Omit for defaults.',
None, description='Extraction options (``strict``). Omit for defaults.'
)
output_save_url: Optional[str] = Field(
None,
Expand Down Expand Up @@ -2096,7 +2089,7 @@ class V2ExtractJobsPostRequest1(BaseModel):
)
options: Optional[V2ExtractOptions] = Field(
None,
description='Extraction options (``strict``, ``grounding``). Omit for defaults. JSON-serialized string in form data.',
description='Extraction options (``strict``). Omit for defaults. JSON-serialized string in form data.',
)
output_save_url: Optional[str] = Field(
None,
Expand Down
14 changes: 4 additions & 10 deletions specs/v2-aide.json
Original file line number Diff line number Diff line change
Expand Up @@ -1312,12 +1312,6 @@
"additionalProperties": false,
"description": "Extraction options (``docs/extract-v2-contract.md`` → Options).",
"properties": {
"grounding": {
"default": true,
"description": "When ``true`` (default), the grounding stage runs and each extracted leaf carries ``ranges`` into the source Markdown where a reference was found (``ranges`` is ``null`` for a value that could not be located). When ``false``, the grounding stage is skipped: every ``extraction_metadata`` leaf has ``ranges: null`` and the request completes faster. Preview.",
"title": "Grounding",
"type": "boolean"
},
"strict": {
"default": false,
"description": "When ``true``, a schema containing fields the model cannot extract fails with a validation error — HTTP 422 on the sync route, or a failed job (``status: \"failed\"``) on the async ``/jobs`` route. When ``false`` (default), unsupported fields are skipped and extraction continues.",
Expand Down Expand Up @@ -5246,7 +5240,7 @@
}
],
"default": null,
"description": "Extraction options (``strict``, ``grounding``). Omit for defaults."
"description": "Extraction options (``strict``). Omit for defaults."
},
"schema": {
"additionalProperties": true,
Expand Down Expand Up @@ -5327,7 +5321,7 @@
}
],
"default": null,
"description": "Extraction options (``strict``, ``grounding``). Omit for defaults. JSON-serialized string in form data."
"description": "Extraction options (``strict``). Omit for defaults. JSON-serialized string in form data."
},
"schema": {
"additionalProperties": true,
Expand Down Expand Up @@ -5632,7 +5626,7 @@
}
],
"default": null,
"description": "Extraction options (``strict``, ``grounding``). Omit for defaults."
"description": "Extraction options (``strict``). Omit for defaults."
},
"output_save_url": {
"anyOf": [
Expand Down Expand Up @@ -5741,7 +5735,7 @@
}
],
"default": null,
"description": "Extraction options (``strict``, ``grounding``). Omit for defaults. JSON-serialized string in form data."
"description": "Extraction options (``strict``). Omit for defaults. JSON-serialized string in form data."
},
"output_save_url": {
"anyOf": [
Expand Down
32 changes: 32 additions & 0 deletions tests/api_resources/v2/test_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,38 @@ def test_extract_sync_strict_option() -> None:
assert req["markdown_url"] == "https://x/y.md"


@respx.mock
def test_extract_options_carries_only_strict() -> None:
# `V2ExtractOptions` is `additionalProperties: false` upstream and `strict` is
# its only member: the spec briefly carried a second option (`grounding`) and
# has since dropped it, so anything the SDK folds in beyond `strict` would now
# be rejected as an unknown key. Pin the exact key set rather than just
# `strict`, so a stray option reintroduced here fails at this seam instead of
# against the live gateway.
client = LandingAIADE(apikey=APIKEY)
route = respx.post("https://api.ade.landing.ai/v2/extract").mock(
return_value=httpx.Response(200, json=EXTRACT_BODY)
)
client.v2.extract(schema={"type": "object"}, markdown="# doc", strict=False)
req = json.loads(route.calls.last.request.content)
assert set(req["options"]) == {"strict"}


def test_extract_job_create_options_carries_only_strict(monkeypatch: pytest.MonkeyPatch) -> None:
# Same `additionalProperties: false` contract on the async job route, which
# shares `_build_extract_body` with the sync one.
client = LandingAIADE(apikey=APIKEY)
captured: Dict[str, Any] = {}

def fake_post(path: str, *, cast_to: Any, body: Any = None, options: Any = None, **kwargs: Any) -> Any: # noqa: ARG001
captured["body"] = body
return {"job_id": "e1", "status": "pending"}

monkeypatch.setattr(client.v2.extract_jobs, "_post", fake_post)
client.v2.extract_jobs.create(schema={"type": "object"}, markdown="x", strict=True)
assert set(captured["body"]["options"]) == {"strict"}


@respx.mock
def test_extract_requires_a_markdown_source() -> None:
# api.md documents the contract: provide exactly one of markdown /
Expand Down