Skip to content

Add preview document extraction providers with offline acceptance - #82

Open
luisquintanilla wants to merge 7 commits into
mainfrom
luisquintanilla-toolkit-extraction-providers
Open

luisquintanilla wants to merge 7 commits into
mainfrom
luisquintanilla-toolkit-extraction-providers

Conversation

@luisquintanilla

@luisquintanilla luisquintanilla commented Nov 1, 2026 •

Copy link
Copy Markdown

Summary

Add five independently consumable, unreleased
CommunityToolkit.DocumentExtraction.* provider packages: PdfPig,
AzureContentUnderstanding, AzureDocumentIntelligence, Mistral and Vision.
All target .NET 8 and .NET 10 and derive from DocumentExtractionClient.
They exchange the shared Documents model; they do not depend on DataIngestion.

Keep the component together in this PR. Shared helpers, public-source bootstrap,
dependency configuration and owning CI are common infrastructure, not a new
public abstraction package. The providers have no dependencies on each other's
Toolkit packages.

Contract and mapping boundaries

Use the Documents input from
dotnet/extensions#7835,
commit e08fe2eae1529a763d0d8d2e42a8d1feae153ed6, as the locked input
for this preparation. Extraction comes from
dotnet/extensions#7588.
This is not a claim that either upstream API has formal approval or that its
packages have been publicly released.

The client performs completed-document extraction from a Stream.
There is no old-interface shim, page-streaming/Pipes addition, second document
model, renderer API, MEDI policy or shared operation/output/artifact framework.
Middleware preserves the delegating-client/builder composition pattern.
Caller streams and supplied SDK, HTTP and chat clients remain borrowed.

Mappings preserve ordered sections and mixed-content table cells, text roles,
table spans, measured geometry where reported, original page associations,
and explicit native inspection:

Provider Mapping/native boundary
PdfPig Native segmentation and measured PDF geometry; no disposed parser exposed as a usable root.
Azure Content Understanding Exact native result with original page/polygon observations and ordered cell content.
Azure Document Intelligence Exact native result with reported units, regions, figures, spans and ordered cell content.
Mistral Cloned native JSON survives response parsing; page-associated text and reported image geometry remain distinct.
Vision Exact response or ordered response array; separate structured/freeform paths and owned native rasterizer cleanup. No fabricated measured text polygons.

Image OCR/transcription is not a caption. Ancestors do not duplicate aggregate
child text. Native inspection is not a durable wire archive or a guarantee of
independence from borrowed provider lifetimes. Unsupported structured Vision
results are rejected without an extra fallback inference request.

Reproducible public prerequisite

The temporary bootstrap source is
https://github.com/luisquintanilla/extensions.git, pinned to:

  • Commit: c03481c76b63c6820e1de6ed9da09d5ff1e9f495.
  • Tree: f2e9d521223db1cf33fa8bcec5667da26b92cc49.
  • Source SDK: 10.0.401.
  • Fresh candidate version: 10.10.0-review.20261104.4.

The integrated commit preserves the locked Documents blobs and the accepted
abstract Extraction client in one public source tree. It replaces the older
interface-based bootstrap input; repinning to the earlier Extraction head
alone would have omitted the locked mixed-cell fixes.

Build the four original owning projects with their normal imports: AI
Abstractions, Documents Abstractions, Extraction Abstractions and Extraction.
Preserve the existing command-scoped Features= recipe. No replacement project,
handwritten nuspec, private overlay/feed/configuration, copied production DLL,
global SDK change or provider/native SDK upgrade is introduced.

Windows CI builds those actual packages and transfers them through a same-run
artifact to the Windows/Linux offline acceptance jobs. The candidate version is
not claimed available on a public package feed. Artifacts are not a NuGet
release or a permanent feed. Independently source-built archives are not
claimed byte-identical to earlier sealed private packages.

Readiness evidence

Current provider head:
db67441fd2b6cc94d2ff745d8172b705f2cf4e54.
The readiness change touches five configuration/documentation files, not
provider implementations, native dependencies or the global SDK pin.

A fresh local public-source bootstrap packed all four original core projects.
Provider restore used the committed pins, followed by a nonincremental Release
build with zero warnings/errors, all ten test binaries passing, and inspection
of all five original provider packages. The local verification also checked
30 actual test-runtime core DLL/archive matches.

Provider workflow 41925266184
completed SUCCESS for this head. All three jobs passed: pinned public
upstream packing, Windows offline acceptance and Ubuntu offline acceptance.
Both platform jobs consumed the actual same-run core artifact and passed
restore, build, offline tests, pack and upload. Each provider build reported
zero warnings/errors.

Provider net8.0 passed net10.0 passed
PdfPig 15 15
Azure Content Understanding 81 81
Azure Document Intelligence 90 90
Mistral 71 71
Vision 81 81
Total 338 338

Local, Windows and Ubuntu each passed 676/676, zero failures/skips.
These repeat the same suites on different environments, not 2,028 distinct test
cases. Hosted counts were read from the actual ten summaries on each platform.

Downloaded and inspected actual nonexpired artifacts from this run:

  • document-extraction-core-source-packages, ID 13368927169.
  • document-extraction-preview-packages-windows-latest, ID 13368722630.
  • document-extraction-preview-packages-ubuntu-latest, ID 13368433009.

The four core main archives contain twenty TFM DLL assets and record the pinned
upstream commit. Each platform artifact contains five provider main archives
at 1.0.0-preview.1, with both target DLLs, MIT licensing, packaged READMEs and
the fresh .4 core dependencies. No packages were released.

GitHub tests the PR merge commit
313e3e6f465a1919502468441a462e368b043a15; provider/bootstrap/root build inputs
match this head. Its seven additional files are unrelated MEVD/Sqlite changes.
No cross-build archive equality or separate portable-PDB audit is claimed.

Historical private .3 acceptance and the older interface-based 512-case
hosted run remain separate evidence; neither substitutes for this new gate.

Review order

  1. Public prerequisite provenance, dependency pins, bootstrap and owning CI.
  2. Shared internal helpers and component configuration.
  3. PdfPig as the smallest local implementation.
  4. The Azure pair with shared SDK transport/replay and mapping tests.
  5. Mistral HTTP/JSON ownership and mapping.
  6. Vision structured/freeform behavior and native rendering-resource handling.

Release boundary

Offline replay validates mapping, contracts and resource handling, not live
recognition quality. Review readiness does not authorize upstream merges,
formal API approval, signing or package publication. MEDI remains a separate
consumer and is not a dependency of this PR.

Attribution

Useful native segmentation, geometry/role mapping and rendering-resource/test
patterns recover attributed work from closed, unmerged
CommunityToolkit/AI#3
(37b34b1fbf0a6f265257f4b46d75b687e3cf0684) and
CommunityToolkit/AI#14
(f7ec65479ce9afe172579fb884d67f534dcf73e8).
Their obsolete OCR abstraction, MEDI policy/enrichment flow, scanned-page
placeholders and text-only rendering are not revived.

Implement PdfPig, Azure Content Understanding, Azure Document Intelligence, Mistral, and Vision adapters with offline acceptance and a pinned public-source core bootstrap.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment on lines +339 to +350
private static ReadOnlyMemory<byte>? DecodeImage(JsonElement image)
{
if (!image.TryGetProperty("image_base64", out JsonElement base64)
|| base64.ValueKind != JsonValueKind.String)
{
return null;
}

string raw = base64.GetString()!;
int comma = raw.IndexOf(',');
return Convert.FromBase64String(comma >= 0 ? raw[(comma + 1)..] : raw);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's an honor to get to review one of your PRs.

Mistral returns images with their own type, but here they always end up as image/png.

The image_base64 field already contains the type, for example data:image/jpeg;base64,... for img-0.jpeg. You can see it in Mistral's OCR notebook. DecodeImage strips that part, and on line 249 the type is always image/png. So a JPEG is marked as PNG.

To fix it, pass the value to new DataContent(raw) when it starts with data:. It reads the type itself.

Suggested change
private static ReadOnlyMemory<byte>? DecodeImage(JsonElement image)
{
if (!image.TryGetProperty("image_base64", out JsonElement base64)
|| base64.ValueKind != JsonValueKind.String)
{
return null;
}
string raw = base64.GetString()!;
int comma = raw.IndexOf(',');
return Convert.FromBase64String(comma >= 0 ? raw[(comma + 1)..] : raw);
}
private static DataContent? DecodeImage(JsonElement image)
{
if (!image.TryGetProperty("image_base64", out JsonElement base64)
|| base64.ValueKind != JsonValueKind.String)
{
return null;
}
string raw = base64.GetString()!;
return raw.StartsWith("data:", StringComparison.OrdinalIgnoreCase)
? new DataContent(raw)
: new DataContent(Convert.FromBase64String(raw), "image/png");
}

I tried it on this branch, together with the call site change below, on .NET 8 and .NET 10. The Mistral tests pass, plus a JPEG test that fails without the fix. Invalid base64 is still rejected.

Comment on lines +245 to +249
ReadOnlyMemory<byte>? content = DecodeImage(image);
ExtractionDocumentSourceReference source = CreateImageSource(sourcePage, image);
ProviderAdapterHelpers.AddElement(section, new ExtractionDocumentImageElement()
{
Content = content is null ? null : new DataContent(content.Value, "image/png"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And at the call site:

Suggested change
ReadOnlyMemory<byte>? content = DecodeImage(image);
ExtractionDocumentSourceReference source = CreateImageSource(sourcePage, image);
ProviderAdapterHelpers.AddElement(section, new ExtractionDocumentImageElement()
{
Content = content is null ? null : new DataContent(content.Value, "image/png"),
DataContent? content = DecodeImage(image);
ExtractionDocumentSourceReference source = CreateImageSource(sourcePage, image);
ProviderAdapterHelpers.AddElement(section, new ExtractionDocumentImageElement()
{
Content = content,

luisquintanilla and others added 6 commits November 2, 2026 10:33
Consume the sealed 20261102.1 core, remove shared response-capture graph, preserve source frames and native inspection, and retain sole text payloads with independent image descriptions and resource safety.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Map explicit Azure cell children with validated deferred ownership. Migrate all five providers to DocumentExtractionClient while preserving service lookup and resource ownership. Document the preview break and pending public dependency coordination.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pin the integrated public source commit and tree, build four original core packages at fresh review version .4, and synchronize provider dependency and same-run artifact selection. Preserve native provider dependencies and document source-built preview provenance.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants