Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
22 changes: 19 additions & 3 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Methods:

The `client.v2` sub-client targets LandingAI's next-generation ADE gateway, which lives on its own host (`api.ade.[env].landing.ai`) rather than the V1 host (`api.va.[env].landing.ai`). It is **additive**: `client.v2.*` is a separate surface from the top-level `client.*` (V1) methods documented above, and using it does not change any V1 behavior. See the [README](README.md#environments) for environment selection and usage examples.

`client.v2.parse_jobs`, `client.v2.extract_jobs`, and `client.v2.ground_jobs` all return a single, unified <a href="./src/landingai_ade/types/v2/job.py">`Job`</a> shape, even though the underlying parse/extract/ground job envelopes differ upstream -- `Job.raw` retains the full original envelope as an escape hatch for any field not surfaced on the typed model.
`client.v2.parse_jobs`, `client.v2.extract_jobs`, `client.v2.build_schema_jobs`, and `client.v2.ground_jobs` all return a single, unified <a href="./src/landingai_ade/types/v2/job.py">`Job`</a> shape, even though the underlying parse/extract/build-schema/ground job envelopes differ upstream -- `Job.raw` retains the full original envelope as an escape hatch for any field not surfaced on the typed model.
Comment thread
tian-lan-landing marked this conversation as resolved.
Outdated

Types:

Expand All @@ -73,6 +73,10 @@ from landingai_ade.types.v2 import (
Job,
JobError,
JobStatus,
V2BuildSchemaBilling,
V2BuildSchemaMetadata,
V2BuildSchemaResponse,
V2BuildSchemaWarning,
V2ExtractBilling,
V2ExtractMetadata,
V2ExtractResult,
Expand All @@ -96,9 +100,10 @@ from landingai_ade.types.v2 import (
)
```

- <code><a href="./src/landingai_ade/types/v2/job.py">Job</a></code> -- unified job shape: `job_id`, `status` (<code><a href="./src/landingai_ade/types/v2/job.py">JobStatus</a></code>: `pending` / `processing` / `completed` / `failed` / `cancelled`), `created_at`, `completed_at`, `progress`, `result` (a `V2ParseResponse` for parse jobs, a `V2ExtractResult` for extract jobs, or `None` until completion), `error` (<code><a href="./src/landingai_ade/types/v2/job.py">JobError</a></code>), `raw` (the full original envelope as a `dict`), and the `.is_terminal` property.
- <code><a href="./src/landingai_ade/types/v2/job.py">Job</a></code> -- unified job shape: `job_id`, `status` (<code><a href="./src/landingai_ade/types/v2/job.py">JobStatus</a></code>: `pending` / `processing` / `completed` / `failed` / `cancelled`), `created_at`, `completed_at`, `progress`, `result` (a `V2ParseResponse` for parse jobs, a `V2ExtractResult` for extract jobs, a `V2BuildSchemaResponse` for build-schema jobs, or `None` until completion), `error` (<code><a href="./src/landingai_ade/types/v2/job.py">JobError</a></code>), `raw` (the full original envelope as a `dict`), and the `.is_terminal` property.
- <code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParseResponse</a></code> -- `markdown`, `structure`, `grounding`, `metadata` (<code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParseMetadata</a></code>, which nests <code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParseBilling</a></code> and carries `output_markdown_chars`, `range_units`, and `openapi_spec`). `structure` is a typed <code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParseStructure</a></code> tree (`document` → <code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParsePage</a></code> → <code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParseElement</a></code>); each node below the root carries its spatial data inline in a <code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParseNodeGrounding</a></code> (`page`, <code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParseRange</a></code>, <code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParseBox</a></code>, normalized page coordinates), and leaf elements additionally carry an `atomic_grounding` list. With `options.inline_markdown`, each node also carries its `markdown` slice. The legacy top-level `grounding` tree (<code><a href="./src/landingai_ade/types/v2/parse_response.py">V2ParseGrounding</a></code> → `V2ParseGroundingPage` → `V2ParseGroundingElement` → `V2ParseGroundingEntry`) is retained for older gateway responses. Element `type`/page `status` are permissive strings and unknown keys are retained.
- <code><a href="./src/landingai_ade/types/v2/extract_response.py">V2ExtractResult</a></code> -- `extraction`, `extraction_metadata`, `markdown`, `output_ref`, `schema_violation_error` (set when `strict=False` and the schema had unextractable fields), `warnings`, and `metadata` (<code><a href="./src/landingai_ade/types/v2/extract_response.py">V2ExtractMetadata</a></code>, which carries `model_version`, `input_markdown_chars`, `output_extraction_chars`, `range_units`, `openapi_spec`, and nests <code><a href="./src/landingai_ade/types/v2/extract_response.py">V2ExtractBilling</a></code>).
- <code><a href="./src/landingai_ade/types/v2/build_schema_response.py">V2BuildSchemaResponse</a></code> -- `extraction_schema` (the generated JSON Schema serialized as a string) and `metadata` (<code><a href="./src/landingai_ade/types/v2/build_schema_response.py">V2BuildSchemaMetadata</a></code>: `job_id`, `duration_ms`, `openapi_spec`, `filename`/`org_id`/`version` (retained for compatibility), a `warnings` list of <code><a href="./src/landingai_ade/types/v2/build_schema_response.py">V2BuildSchemaWarning</a></code> (`code`, `msg`), and nested <code><a href="./src/landingai_ade/types/v2/build_schema_response.py">V2BuildSchemaBilling</a></code>).
- <code><a href="./src/landingai_ade/types/v2/file_upload_response.py">V2FileUploadResponse</a></code> -- `file_ref`.
- <code><a href="./src/landingai_ade/types/v2/ground_response.py">V2GroundResult</a></code> -- `grounding` (a tree mirroring the input `extraction_metadata`, each `{value, ranges}` leaf replaced by the list of `structure` blocks its ranges overlap) and `metadata` (<code><a href="./src/landingai_ade/types/v2/ground_response.py">V2GroundMetadata</a></code>: `job_id`, `duration_ms`, `openapi_spec`, and nested <code><a href="./src/landingai_ade/types/v2/ground_response.py">V2GroundBilling</a></code>).

Expand Down Expand Up @@ -126,6 +131,17 @@ Methods:

Same polling/timeout semantics as `parse_jobs.wait`. Extract jobs have no `cancelled` status, so `raise_on_failure` only ever triggers on `failed`.

- <code title="post /v2/extract/build-schema">client.v2.<a href="./src/landingai_ade/resources/v2/v2.py">build_schema</a>(\*, markdowns=..., markdown_urls=..., prompt=..., schema=...) -> <a href="./src/landingai_ade/types/v2/build_schema_response.py">V2BuildSchemaResponse</a></code>

Synchronous schema generation. Generate or edit a JSON Schema for extraction from one or more source Markdown documents and/or a natural-language `prompt`, optionally iterating on an existing `schema`. Provide at least one of `markdowns`, `markdown_urls`, `prompt`, or `schema`. `markdowns` entries are each either an inline markdown string or a file upload (`Path`/`bytes`/file object) -- the request is sent as `multipart/form-data` when any entry is a file, and as JSON otherwise. `schema` accepts a pydantic `BaseModel` subclass, a `dict`, or a JSON-encoded string -- all are coerced to a JSON Schema and sent as a JSON-encoded string. Raises `V2SyncTimeoutError` on a 504; use `build_schema_jobs` for long-running inputs.

- <code title="post /v2/extract/build-schema/jobs">client.v2.build_schema_jobs.<a href="./src/landingai_ade/resources/v2/build_schema.py">create</a>(\*, markdowns=..., markdown_urls=..., prompt=..., schema=..., service_tier=...) -> <a href="./src/landingai_ade/types/v2/job.py">Job</a></code>
- <code title="get /v2/extract/build-schema/jobs/{job_id}">client.v2.build_schema_jobs.<a href="./src/landingai_ade/resources/v2/build_schema.py">get</a>(job_id) -> <a href="./src/landingai_ade/types/v2/job.py">Job</a></code>
- <code title="get /v2/extract/build-schema/jobs">client.v2.build_schema_jobs.<a href="./src/landingai_ade/resources/v2/build_schema.py">list</a>(\*, page=..., page_size=..., status=...) -> JobList[<a href="./src/landingai_ade/types/v2/job.py">Job</a>]</code>
- <code>client.v2.build_schema_jobs.<a href="./src/landingai_ade/resources/v2/build_schema.py">wait</a>(job_id, \*, timeout=600, poll_interval=None, raise_on_failure=False) -> <a href="./src/landingai_ade/types/v2/job.py">Job</a></code>

Same polling/timeout semantics as `parse_jobs.wait`. Build-schema jobs have no `cancelled` status, so `raise_on_failure` only ever triggers on `failed`.

- <code title="post /v2/ground">client.v2.<a href="./src/landingai_ade/resources/v2/v2.py">ground</a>(\*, extraction_metadata, structure) -> <a href="./src/landingai_ade/types/v2/ground_response.py">V2GroundResult</a></code>

Synchronous ground. Maps each extracted field back to the `structure` blocks it was quoted from by overlapping `extraction_metadata` ranges against every block's inline `grounding.range`. Both `extraction_metadata` (e.g. `client.v2.extract(...).extraction_metadata`) and `structure` (e.g. `client.v2.parse(...).structure`) accept a `dict` or a pydantic model. Block ids resolve only against the `structure` supplied here, so pass the parse result the extraction actually came from. Raises `V2SyncTimeoutError` on a 504; use `ground_jobs` for long-running inputs.
Expand All @@ -143,5 +159,5 @@ Methods:

Notes:

- `parse_jobs.list` / `extract_jobs.list` / `ground_jobs.list` all return a `JobList` (a `list[Job]` subclass) carrying pagination metadata: `.has_more`, `.org_id`, `.page`, `.page_size`.
- `parse_jobs.list` / `extract_jobs.list` / `build_schema_jobs.list` / `ground_jobs.list` all return a `JobList` (a `list[Job]` subclass) carrying pagination metadata: `.has_more`, `.org_id`, `.page`, `.page_size`.
- All `client.v2.*` methods accept the usual `extra_headers`, `extra_query`, `extra_body`, and `timeout` overrides; sync methods additionally accept `save_to` (parse/extract only, not the job-creation methods) to write the response to disk, mirroring V1's `save_to`.
33 changes: 28 additions & 5 deletions docs/v2-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ what to check when the upstream spec (`specs/v2-aide.json`) changes.

| Layer | Location | What it covers |
| --- | --- | --- |
| Response models | `tests/test_v2_types.py` | Deserialization of `V2ParseResponse` / `V2ExtractResult` / `V2GroundResult` and their nested models from plain dicts, including unknown-key tolerance. |
| Job normalization | `tests/test_v2_normalize.py` | `normalize_parse_job` / `normalize_extract_job` / `normalize_ground_job`: envelope → unified `Job` (status, timestamps, `result`, `error`). |
| Response models | `tests/test_v2_types.py` | Deserialization of `V2ParseResponse` / `V2ExtractResult` / `V2BuildSchemaResponse` / `V2GroundResult` and their nested models from plain dicts, including unknown-key tolerance. |
| Job normalization | `tests/test_v2_normalize.py` | `normalize_parse_job` / `normalize_extract_job` / `normalize_build_schema_job` / `normalize_ground_job`: envelope → unified `Job` (status, timestamps, `result`, `error`). |
Comment thread
tian-lan-landing marked this conversation as resolved.
Outdated
| Resource wiring | `tests/api_resources/v2/` | `respx`-mocked HTTP: host routing, multipart/JSON bodies, options serialization, job polling. No network. |
| Live smoke | `tests/contract/test_v2_smoke.py` | End-to-end calls against staging (marked `contract`; skipped unless `LANDINGAI_ADE_STAGING_APIKEY` is set). |

Expand Down Expand Up @@ -68,6 +68,29 @@ 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
reports `output_url` (on `Job.raw`) instead of an inline `result`.

## Current build-schema-response shape

`POST /v2/extract/build-schema` (and the completed `build_schema_jobs` result)
returns a `V2BuildSchemaResponse` with:

- `extraction_schema` — the generated JSON Schema serialized as a **string** (VTRA
parity — a string, not an object). Parse it with `json.loads(...)` to get the
schema dict.
- `metadata` (`V2BuildSchemaMetadata`) — `job_id`, `duration_ms`, `openapi_spec`,
`filename` / `org_id` / `version` (retained for v1 compatibility, unpopulated in
this version), a `warnings` list of `V2BuildSchemaWarning` (`{code, msg}`, e.g.
code `nonconformant_schema`), and `billing` (`V2BuildSchemaBilling`).

`client.v2.build_schema(...)` generates or edits a JSON Schema from one or more
source Markdown documents and/or a natural-language `prompt`, optionally iterating
on an existing `schema`. At least one of `markdowns`, `markdown_urls`, `prompt`, or
`schema` must be provided. `markdowns` entries are each either an inline markdown
string or a file upload (`Path`/`bytes`/file object); the request is sent as
`multipart/form-data` when any entry is a file and as JSON otherwise. `schema`
accepts a pydantic model, a `dict`, or a JSON string — all coerced to a JSON Schema
and sent as a JSON-encoded string. `build_schema_jobs.create` additionally accepts
`service_tier`.

## Current ground-response shape

`POST /v2/ground` (and the completed `ground_jobs` result) returns a
Expand All @@ -87,9 +110,9 @@ be passed directly). `ground_jobs.create` additionally accepts `output_save_url`

## Async job envelopes

`normalize_parse_job`, `normalize_extract_job`, and `normalize_ground_job` fold
the upstream job envelopes into the unified `Job`. All are tolerant of field-name
drift:
`normalize_parse_job`, `normalize_extract_job`, `normalize_build_schema_job`, and
`normalize_ground_job` fold the upstream job envelopes into the unified `Job`. All
are tolerant of field-name drift:

- The parse response lives under `result` (older envelopes used `data`).
- Failures arrive as a structured `error` object (`{code, message}`); older parse
Expand Down
26 changes: 3 additions & 23 deletions examples/v2_smoke_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

from landingai_ade import LandingAIADE, AsyncLandingAIADE

ALL_CHECKS = ["files", "extract", "extract_jobs", "parse", "parse_jobs"]
ALL_CHECKS = ["extract", "extract_jobs", "parse", "parse_jobs"]

# A tiny self-contained markdown document + schema so extract/files can run without any file.
SAMPLE_MARKDOWN = "# Acme Inc. — Q1 Report\n\nTotal revenue for the quarter was **$1,250,000**.\n"
Expand Down Expand Up @@ -116,7 +116,6 @@ def run_sync(args: argparse.Namespace, checks: List[str]) -> int:
print(f"V1 base: {client.base_url} | V2 base: {client._v2_base_url}\n")

results: dict[str, str] = {}
file_ref: Optional[str] = None

def record(name: str, fn: Callable[[], Any]) -> None:
print(f"── {name} ".ljust(60, "─"))
Expand All @@ -130,15 +129,6 @@ def record(name: str, fn: Callable[[], Any]) -> None:
traceback.print_exc()
print()

if "files" in checks:

def _files() -> Any:
nonlocal file_ref
file_ref = client.v2.files.upload(file=("doc.md", SAMPLE_MARKDOWN.encode(), "text/markdown"))
return f"file_ref={file_ref}"

record("files.upload", _files)

if "extract" in checks:

def _extract() -> Any:
Expand Down Expand Up @@ -208,16 +198,6 @@ async def _main() -> int:
print(f"[async] V1 base: {client.base_url} | V2 base: {client._v2_base_url}\n")
results: dict[str, str] = {}

if "files" in checks:
print("── [async] files.upload ".ljust(60, "─"))
try:
ref = await client.v2.files.upload(file=("doc.md", SAMPLE_MARKDOWN.encode(), "text/markdown"))
results["files.upload"] = "PASS"
print(f" PASS file_ref={ref}\n")
except Exception as exc: # noqa: BLE001
results["files.upload"] = "FAIL"
print(f" FAIL {type(exc).__name__}: {exc}\n")

if "extract" in checks:
print("── [async] v2.extract ".ljust(60, "─"))
try:
Expand Down Expand Up @@ -262,9 +242,9 @@ def main() -> int:
args = _make_parser().parse_args()
checks = _selected(args.only)
if args.use_async:
async_checks = [c for c in checks if c in {"files", "extract", "extract_jobs"}]
async_checks = [c for c in checks if c in {"extract", "extract_jobs"}]
if async_checks != checks:
print("note: --async runs files/extract/extract_jobs only (parse checks are sync-only here)\n")
print("note: --async runs extract/extract_jobs only (parse checks are sync-only here)\n")
return run_async(args, async_checks)
return run_sync(args, checks)

Expand Down
Loading
Loading