Skip to content

Clarify WriteAsync contract: add validation and docs for single-document constraint#7433

Merged
adamsitnik merged 12 commits into
data-ingestion-preview2from
copilot/medi-clarify-ingestionchunkwriter-contract
Jun 25, 2026
Merged

Clarify WriteAsync contract: add validation and docs for single-document constraint#7433
adamsitnik merged 12 commits into
data-ingestion-preview2from
copilot/medi-clarify-ingestionchunkwriter-contract

Conversation

Copilot AI commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

WriteAsync implicitly assumed all chunks belong to a single document (pre-existing keys only fetched once for the first chunk's document). This made the contract ambiguous and error-prone. Instead of adding a new parameter, enforce the contract via runtime validation and clarify it in XML documentation.

Changes

  • IngestionChunkWriter.WriteAsync XML docs: Updated to explicitly state that all chunks must belong to the same document, with an <exception cref="InvalidOperationException"> tag documenting the thrown exception.
  • VectorStoreWriter.WriteAsync validation: Added a ReferenceEquals check that throws InvalidOperationException if any chunk references a different document than the first chunk.

No API signature changes — the WriteAsync method signature remains unchanged.

…to WriteAsync

- Remove Document property and constructor parameter from IngestionChunk<T>
- Add IngestionDocument document parameter to IngestionChunkWriter<T>.WriteAsync
- Update VectorStoreWriter to use the new document parameter
- Update IngestionPipeline to pass document to WriteAsync
- Update all chunkers (DocumentTokenChunker, ElementsChunker, HeaderChunker,
  SectionChunker, SemanticSimilarityChunker) to not pass document to chunks
- Update all tests to match the new API

Agent-Logs-Url: https://github.com/dotnet/extensions/sessions/d041591e-b70e-45f7-9302-c04e4787e92e

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI changed the title [WIP] Clarify the IngestionChunkWriter.WriteAsync contract around documents Clarify WriteAsync contract: move document from IngestionChunk to WriteAsync parameter Mar 28, 2026
Copilot AI requested a review from adamsitnik March 28, 2026 10:29

@adamsitnik adamsitnik left a comment

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 not as bad as I thought it would be.

@roji could you PTAL and let me know if it addresses your feedback from #6970?

@adamsitnik adamsitnik requested a review from roji March 28, 2026 10:59
@adamsitnik adamsitnik marked this pull request as ready for review March 28, 2026 11:03
@adamsitnik adamsitnik added this to the Data Ingestion Preview 2 milestone Mar 28, 2026
Comment thread src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/IngestionChunk.cs Outdated

@adamsitnik adamsitnik left a comment

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.

@copilot please address my feedback

Comment thread src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/IngestionChunk.cs Outdated
…sync

Addresses review feedback:
- Restore Document property on IngestionChunk (revert removal)
- Keep IngestionDocument document as explicit parameter on WriteAsync
- Make document the first argument of WriteAsync
- Only 4 files changed vs base: IngestionChunkWriter, VectorStoreWriter,
  IngestionPipeline, VectorStoreWriterTests

Agent-Logs-Url: https://github.com/dotnet/extensions/sessions/1e9e1aa4-84c0-4e3c-a6b1-b1f90cafeda3

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

Copilot AI commented Apr 2, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please address my feedback

Done in fbc1730Document restored on IngestionChunk, document is now the first argument of WriteAsync, and all reverted files show an empty diff vs base.

Copilot AI changed the title Clarify WriteAsync contract: move document from IngestionChunk to WriteAsync parameter Clarify WriteAsync contract: add explicit IngestionDocument parameter to WriteAsync Apr 2, 2026
Copilot AI requested a review from adamsitnik April 2, 2026 13:48

@adamsitnik adamsitnik left a comment

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.

LGTM, @roji PTAL one more look

@adamsitnik adamsitnik requested a review from roji April 2, 2026 13:58

@roji roji left a comment

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.

Thanks, PTAL at some thoughts on whether introducing the new parameter makes sense here (or just renaming the method).

/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task representing the asynchronous write operation.</returns>
public abstract Task WriteAsync(IAsyncEnumerable<IngestionChunk<T>> chunks, CancellationToken cancellationToken = default);
public abstract Task WriteAsync(IngestionDocument document, IAsyncEnumerable<IngestionChunk<T>> chunks, CancellationToken cancellationToken = default);

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.

Optional: consider renaming to WriteDocumentAsync rather than adding the document parameter: writing a document is what this function actually does.

I think renaming makes more sense than adding the new document parameter - the parameter has to always be exactly the same as the document referenced by all chunks in the 2nd parameter; in other words, the 1st parameter is useless: it's only there for documentation and/or validation purposes (see below).

But even if you do decide to keep the new parameter (which I'd recommend against), I'd still consider renaming to make the API extra-clear.

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.

the parameter has to always be exactly the same as the document referenced by all chunks in the 2nd parameter; in other words, the 1st parameter is useless: it's only there for documentation and/or validation purposes (see below).

That is why the original design did not have it ;P

Optional: consider renaming to WriteDocumentAsync rather than adding the document parameter: writing a document is what this function actually does.

Hmm let me think loud about it:

We have IngestionDocumentReader that comes with ReadAsync (not ReadDocumentAsync) that reads a document.
Then we have a IngestionChunkWriter that comes with WriteAsync. It's not called IngestionDocumentWriter because it does not write the document, but the chunks that were created from a document. Because of that I don't think that the method should be called WriteDocumentAsync.

So perhaps we should not rename anything but add doc remarks that clarify that all the chunks belong to a single document?

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.

For one thing, we could simply add validation. That is, if WriteAsync is passed chunks from multiple documents, it throws - should be easy to do, right?

Regardless, I'm a big believer in API names clearly expressing their behavior/semantics; so it feels like somehow the API naming should express that there's a single-document assumption/restriction here.

So... With IngestionDocumentReader, presumably ReadAsync just returns a document type, so there's much less need to clarify anything via naming (the type already does that). But on the writer side things are more complicated, since we receive chunks as input, but there's an unwritten limitation that they have to belong to the same document. That's an important difference IMHO.

So I'd still consider calling the method WriteDocumentChunksAsync, or WriteDocumentAsync if we want to prioritize brevity (the parameter type already tells you that the "document" here is passed in the form of a bunch of chunks).

If we want to 100% lock down this assumption at the type level, we can also rename the type from IngestionChunkWriter to IngestionDocumentChunkWriter (or again, IngestionDocumentWriter to prioritize brevity). I'd do this only if you're 100% sure that you'll never add an API that allows e.g. mixing chunks from different documents; we can do that, but it's maybe a strong commitment to make for the future... It might be safer to only change the naming on the method level, this way if we ever want to add another method which allows multiple documents, we can.

The way I see it, at the end of the day the WriteAsync method accepts and writes a document to the database; it happens to have already been decomposed into chunks, but that's just a form/shape/format for that document.

BTW your ingestion data model might have been different here, with the document actually holding a list of its chunks; if that were the case, you'd just pass in the document directly as the only parameter and everything would be 100% crystal-clear. But since the document (currently) doesn't reference its chunks, we pass the chunks instead as a replacement for the document; but what's written is still the document.

What do you think?

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.

For one thing, we could simply add validation. That is, if WriteAsync is passed chunks from multiple documents, it throws - should be easy to do, right?

This is what I ended up doing.

Comment thread src/Libraries/Microsoft.Extensions.DataIngestion/Writers/VectorStoreWriter.cs Outdated

@adamsitnik adamsitnik left a comment

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.

@copilot please make following changes:

  1. Revert all your changes.
  2. Sync the branch with data-ingestion-preview2
  3. Modify src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/IngestionChunkWriter.cs to do the following:

if WriteAsync is passed chunks from multiple documents, it throws

  1. Update IngestionChunkWriter.WriteAsync xml docs and make it clear it supports only chunks that belong to the same document.

@adamsitnik adamsitnik left a comment

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.

@copilot please make following changes:

  1. Modify src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/IngestionChunkWriter.cs to do the following:

if WriteAsync is passed chunks from multiple documents, it throws

  1. Update IngestionChunkWriter.WriteAsync xml docs and make it clear it supports only chunks that belong to the same document.
  2. Update the PR description

@dotnet-policy-service dotnet-policy-service Bot added the waiting-author-feedback 📭 The author of this issue needs to respond in order for us to continue investigating this issue. label Jun 22, 2026

Copilot AI commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please make following changes:

  1. Modify src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/IngestionChunkWriter.cs to do the following:...

Done in b9c0e08:

  1. Updated IngestionChunkWriter.WriteAsync XML docs to state all chunks must belong to the same IngestionDocument, with <exception cref="InvalidOperationException"> tag.
  2. Added ReferenceEquals validation in VectorStoreWriter.WriteAsync that throws if chunks from different documents are encountered.
  3. PR description updated.

@dotnet-policy-service dotnet-policy-service Bot removed the waiting-author-feedback 📭 The author of this issue needs to respond in order for us to continue investigating this issue. label Jun 22, 2026
Copilot AI requested a review from adamsitnik June 22, 2026 12:09

@adamsitnik adamsitnik left a comment

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.

@copilot add a basic unit test that verifies that

Copilot AI commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot add a basic unit test that verifies that

Added in the latest commit — ThrowsWhenChunksFromMultipleDocuments test in VectorStoreWriterTests that verifies WriteAsync throws InvalidOperationException when passed chunks from different documents.

Copilot AI requested a review from adamsitnik June 22, 2026 12:20
Comment thread src/Libraries/Microsoft.Extensions.DataIngestion/Writers/VectorStoreWriter.cs Outdated
Copilot AI requested a review from adamsitnik June 22, 2026 15:52

@adamsitnik adamsitnik left a comment

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.

LGTM now

@dotnet-comment-bot

Copy link
Copy Markdown
Collaborator

‼️ Found issues ‼️

Project Coverage Type Expected Actual
Microsoft.Extensions.Diagnostics.Testing Line 99 98.65 🔻
Microsoft.Extensions.Telemetry Line 93 91.95 🔻
Microsoft.Extensions.AI Line 89 88.65 🔻
Microsoft.Extensions.AI Branch 89 88.57 🔻
Microsoft.Extensions.AI.OpenAI Line 75 62.89 🔻
Microsoft.Extensions.AI.OpenAI Branch 75 50.41 🔻
Microsoft.Extensions.DataIngestion.Abstractions Branch 75 72.73 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Line 75 4.46 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Branch 75 0 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Line 99 96.03 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Branch 99 94.39 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes Line 99 97.73 🔻
Microsoft.Extensions.ServiceDiscovery.Dns Line 75 68.32 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Line 75 42.11 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Branch 75 42.86 🔻
Microsoft.Extensions.ServiceDiscovery Line 75 67.36 🔻
Microsoft.Extensions.ServiceDiscovery Branch 75 71.43 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Line 75 73.85 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Branch 75 70 🔻
Microsoft.Extensions.VectorData.Abstractions Line 75 37.39 🔻
Microsoft.Extensions.VectorData.Abstractions Branch 75 22.73 🔻

🎉 Good job! The coverage increased 🎉
Update MinCodeCoverage in the project files.

Project Expected Actual
Microsoft.Gen.BuildMetadata 97 100
Microsoft.Gen.MetadataExtractor 57 73
Microsoft.Gen.MetricsReports 67 69
Microsoft.Extensions.AI.Abstractions 82 85
Microsoft.Extensions.AI.Evaluation.NLP 0 78
Microsoft.Extensions.Caching.Hybrid 82 84
Microsoft.Extensions.DataIngestion 75 87
Microsoft.Extensions.DataIngestion.Markdig 75 90
Microsoft.Extensions.Http.Resilience 97 100

Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1479765&view=codecoverage-tab

@adamsitnik adamsitnik merged commit c5fb536 into data-ingestion-preview2 Jun 25, 2026
6 checks passed
@adamsitnik adamsitnik deleted the copilot/medi-clarify-ingestionchunkwriter-contract branch June 25, 2026 07:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MEDI] Clarify the IngestionChunkWriter.WriteAsync contract around documents

4 participants