feat: Add Cognita provider and layout adapter for document parsing - #148
feat: Add Cognita provider and layout adapter for document parsing#148RHL-RWT-01 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings affect parsing, retries, configuration, and layout evaluation.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds Cognita as a self-hosted PARSE provider with Markdown export, layout normalization, pipeline registration, and evaluation support.
Changes:
- Adds Cognita API integration and output normalization.
- Registers the Cognita pipeline, provider, layout enum, and adapter.
- Adds helper unit tests.
File summaries
| File | Summary and final review findings |
|---|---|
tests/parse_bench/inference/providers/parse/test_cognita.py |
Adds helper tests. Nit (1 vote): expand coverage to provider, normalization, and adapter behavior. |
src/parse_bench/schemas/layout_detection_output.py |
Adds COGNITA_LAYOUT. Moderate (1 vote): add corresponding LAYOUT_MODEL_INFO metadata. |
src/parse_bench/inference/providers/parse/cognita.py |
Implements the provider and normalization. Critical (2 votes): avoid converting table-like content inside fenced code blocks. Moderate (1 vote each): handle HTTP 429 as rate limiting, convert fallback page Markdown, support tables without outer pipes, and recurse through nested lists. |
src/parse_bench/inference/providers/parse/__init__.py |
Registers the provider module. |
src/parse_bench/inference/pipelines/parse.py |
Registers the Cognita pipeline. Moderate (3 votes): allow COGNITA_SERVER_URL to override pipeline configuration. |
src/parse_bench/evaluation/layout_adapters/adapters.py |
Adds the Cognita layout adapter. Moderate (2 votes): use selected-page dimensions; moderate (1 vote): require Cognita-specific output or provider identity during fallback matching. |
Review details
Suppressed comments (12)
src/parse_bench/evaluation/layout_adapters/adapters.py:3474
- Using
seg.confidence or 1.0turns a valid confidence of0.0into a perfect score of1.0. That changes Cognita's confidence semantics and can inflate score-weighted layout evaluation; preserve zero by checking forNoneexplicitly.
score=float(seg.confidence or 1.0),
src/parse_bench/evaluation/layout_adapters/adapters.py:3427
- This fallback matcher accepts every
ParseOutputthat haslayout_pages. If provider resolution is unavailable,create_layout_adapter_for_resultcan therefore select Cognita for unrelated parse providers and label their outputCOGNITA_LAYOUT. Require a Cognita-specific raw-output marker such aspage_markdowns/parse, or a provider identity check here.
def matches(cls, inference_result: InferenceResult) -> bool:
return isinstance(inference_result.output, ParseOutput) and bool(inference_result.output.layout_pages)
src/parse_bench/inference/providers/parse/cognita.py:207
- This default sends every input document to the public
https://api.cognita.rahulrawat.inservice, which contradicts the PR's advertised self-hosted/no-cloud behavior. Cognita's documented default deployment also requiresX-API-Key, but this pipeline does not configure one, so an unconfigured run can fail with 401; use a local default or require an explicitly configured endpoint and credential.
self.base_config.get("server_url") or os.getenv("COGNITA_SERVER_URL") or "https://api.cognita.rahulrawat.in"
src/parse_bench/inference/providers/parse/cognita.py:330
- Cognita table blocks store their cell content under
table.rows; they do not populateblock["text"]. This therefore creates aTableitem with emptyvalue/md, and the adapter's_build_vendor_contentreturnsNone, soto_attribution_blocksskips the table prediction entirely. Serialize the structured table to HTML/text (or preserve it inhtml/md) before constructing theLayoutItemIR.
type=_ITEM_TYPE_BY_LABEL.get(label, "text"),
value=str(block.get("text", "")),
md=str(block.get("text", "")),
bbox=seg,
layout_segments=[seg],
src/parse_bench/inference/providers/parse/cognita.py:260
- When any per-page export returns a non-200 response or raises an HTTP error, this fallback concatenates only
block["text"]. Cognita table blocks keep their content intable.rows, so those pages lose their tables; ParseBench evaluates page markdown for table metrics, making the run silently report incomplete output. Retry/fail the export or build a structured fallback that preserves table content.
# Fall back to raw block text if export is unavailable for this page.
return "\n\n".join(str(b.get("text", "")) for b in page.get("blocks", []) if b.get("text"))
src/parse_bench/inference/providers/parse/cognita.py:277
- This performs one blocking
/v1/exportrequest per page after the initial parse. The advertised 2,079-page run therefore adds 2,079 sequential HTTP calls, increasing latency, server load, and the chance of partial export failures linearly with document size. Prefer a batched/per-document page export or avoid re-rendering when a page-level representation is already available.
page_markdowns = [self._export_page_markdown(client, document, page) for page in pages]
src/parse_bench/inference/providers/parse/cognita.py:236
- HTTP 429 falls through this 4xx branch and becomes
ProviderPermanentError, but the inference runner retries onlyProviderTransientErrorandProviderRateLimitError. Rate-limited Cognita calls therefore fail immediately instead of participating in the standard retry/backoff path; classify 429 asProviderRateLimitErrorand import that exception.
if 400 <= resp.status_code < 500:
raise ProviderPermanentError(f"Cognita parse failed ({resp.status_code}): {resp.text[:300]}")
src/parse_bench/inference/providers/parse/cognita.py:346
- When the parse response has no whole-document Markdown, this fallback joins the raw
page_markdownseven though each page was converted to HTML at line 311. Any fallback GFM tables therefore remain pipe syntax and are invisible to ParseBench's table metrics. Join the converted page Markdown or run_gfm_tables_to_htmlover this joined value.
# Fall back to the whole-document markdown if per-page export produced nothing.
if not full_markdown and page_markdowns:
full_markdown = "\n\n".join(page_markdowns)
src/parse_bench/inference/providers/parse/cognita.py:85
- Valid GFM tables may omit the outer pipes (
A | B/--- | ---), but this regex requires both a leading and trailing pipe. Those tables pass through as Markdown and are not discoverable by the HTML-based table metrics. Match rows containing a pipe while keeping the separator-row check.
_PIPE_ROW_RE = re.compile(r"^\s*\|.*\|\s*$")
src/parse_bench/inference/providers/parse/cognita.py:195
- Only direct children of a page-level list are emitted. Cognita's IR permits nested lists under
list_item, so nested list/list-item blocks and their bboxes are silently omitted fromlayout_pages. Recurse through list children instead of flattening only one level.
if str(b.get("type") or "").lower() == "list" and isinstance(b.get("children"), list):
children = [c for c in b["children"] if isinstance(c, dict)]
if children:
out.extend(children)
continue
src/parse_bench/schemas/layout_detection_output.py:327
- The new enum member has no corresponding entry in
LAYOUT_MODEL_INFO, unlike the existing layout models. Consumers that enumerate or index this metadata table will omit Cognita or raise a lookup error; add Cognita's display metadata alongside the other entries.
COGNITA_LAYOUT = "cognita_layout"
tests/parse_bench/inference/providers/parse/test_cognita.py:15
- The new tests cover only pure helper functions; they do not exercise
CognitaProvider.run_inference/normalizeorCognitaLayoutAdapterwith a mocked response. HTTP status handling, export fallback, table-block mapping, page filtering, and confidence conversion can therefore regress without detection; add provider and adapter tests comparable totest_liteparse_layout.py.
def test_gfm_table_converted_to_html_prose_untouched() -> None:
md = "# Title\n\nSome prose.\n\n| A | B |\n| --- | --- |\n| 1 | 2 |\n| 3 | 4 |\n\nAfter."
out = _gfm_tables_to_html(md)
- Files reviewed: 6/6 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Skip fenced code blocks in the GFM->HTML table converter so pipe/ separator lines inside code are not rewritten as <table>. - Use the scored page (page_filter) as the reference frame in CognitaLayoutAdapter, so mixed-size documents normalize bboxes against the correct page dimensions. - Drop the hard-coded server_url from the cognita pipeline config so COGNITA_SERVER_URL can override it (provider keeps default/env fallback). Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
Hi @boyang-zhang1 , could you please approve the pending workflow for this PR? |
/v1/parsefor the IR, renders faithful per-page Markdown via/v1/export, and emitslayout_pageswith normalized bboxes + Canonical17 labels (GFM tables converted to HTML so table metrics score). Includes acognitapipeline, aCognitaLayoutAdapter(+COGNITA_LAYOUTenum), and 8 unit tests.