From c28440aa93d61569dd590568a2fe043a4a268125 Mon Sep 17 00:00:00 2001 From: "spec-sync[bot]" Date: Mon, 14 Sep 2026 23:02:08 +0000 Subject: [PATCH 1/2] chore(spec-sync): update V2 spec snapshot + regenerated reference models --- specs/_generated/v2_models.py | 15 ++++----------- specs/v2-aide.json | 14 ++++---------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/specs/_generated/v2_models.py b/specs/_generated/v2_models.py index 81c21cf..c05e7a3 100644 --- a/specs/_generated/v2_models.py +++ b/specs/_generated/v2_models.py @@ -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.', @@ -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( ..., @@ -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( ..., @@ -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, @@ -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, diff --git a/specs/v2-aide.json b/specs/v2-aide.json index 1bfa2c5..3e81cec 100644 --- a/specs/v2-aide.json +++ b/specs/v2-aide.json @@ -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.", @@ -5246,7 +5240,7 @@ } ], "default": null, - "description": "Extraction options (``strict``, ``grounding``). Omit for defaults." + "description": "Extraction options (``strict``). Omit for defaults." }, "schema": { "additionalProperties": true, @@ -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, @@ -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": [ @@ -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": [ From 85893c070aeacbd5d2f08c9d53ff370bad3b19f6 Mon Sep 17 00:00:00 2001 From: "spec-sync[bot]" Date: Mon, 14 Sep 2026 23:04:34 +0000 Subject: [PATCH 2/2] feat(spec-sync): wire client.v2 to spec diff (AI) --- docs/v2-testing.md | 34 +++++++++++++------------- tests/api_resources/v2/test_extract.py | 32 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/docs/v2-testing.md b/docs/v2-testing.md index 3c4f74d..2080f5b 100644 --- a/docs/v2-testing.md +++ b/docs/v2-testing.md @@ -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. + +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 diff --git a/tests/api_resources/v2/test_extract.py b/tests/api_resources/v2/test_extract.py index e021549..fcfefbd 100644 --- a/tests/api_resources/v2/test_extract.py +++ b/tests/api_resources/v2/test_extract.py @@ -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 /