fix: remap chunk results after skipped messages - #283
Kevin Turcios (KRRT7) wants to merge 7 commits into
Conversation
…Result docstring The success property was removed in a previous commit but the docstring still referenced it, causing confusion.
Propagate shutdown_event to dispatcher for coordinated pipeline shutdown. When shutdown is requested, skip remaining chunks instead of processing them after the producer has stopped.
30d6b02 to
004ed74
Compare
004ed74 to
1da5fff
Compare
|
Bernhard Merkle (@bmerkle) robgruen if you wouldn't mind reviewing, thanks |
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_streaming_skips_failed_message_and_commits_next_message() -> None: |
There was a problem hiding this comment.
I would maybe add one more test that includes a repair & reimport to make sure there are no duplicates.
something like this: fail -> repair -> replay -> replay
There was a problem hiding this comment.
I've pushed new tests for that, hopefully they're good.
Adds the fail -> repair -> replay -> replay coverage requested in review, to confirm a repaired reimport does not duplicate messages. - ControlledExtractor gains fail_on_text: chunks are extracted concurrently, so call-index-based failure cannot deterministically target a specific message. The set is mutable so a test can "repair" the extractor. - _replay mirrors what importers do (tools/ingest_email.py): pre-filter already-ingested source IDs via are_sources_ingested, then stream the rest. - test_streaming_replay_after_repair_ingests_each_source_once: the skipped message's source is never marked ingested, so a repaired replay picks up exactly that message and a second replay is a no-op. - test_streaming_replay_without_repair_does_not_duplicate: replaying while the failure persists re-skips rather than duplicating.
Bernhard Merkle (bmerkle)
left a comment
There was a problem hiding this comment.
Review summary
Ran a multi-pass review (correctness, test coverage, comment accuracy, error handling) against the diff. The core fix is sound, but the specific bug this PR claims to fix is not actually covered by a test, and there's one error-handling inconsistency worth closing before merge.
Critical Issues
None. The positional remap in _commit_batch_from_chunk_results was traced against the reassembler's invariants (contiguous per-message result grouping in chunk_results, monotonic ordinals, zero-chunk-message handling) and is correct given how add_messages.py's producer/reassembler currently stage results.
Important Issues
- The remap this PR fixes has no test verifying it end-to-end. Every extractor in the test suite returns an empty
KnowledgeResponse, soadd_knowledge_batch_to_semantic_ref_indexnever actually produces aSemanticRef, and no test readstranscript.semantic_refs/semantic_ref_indexafter a skip. A regression that reverted just theknowledge_itemsremap (lines 344-351) would pass every test currently in this PR. Suggest adding a test with a non-emptyKnowledgeResponse(e.g. one entity per chunk, named after the chunk text) across a skip, then asserting the resulting semref'smessage_ordinalpoints at the correct storage message, not the source-stream one. - Narrow scenario coverage. All skip tests cover exactly one skip of a single-chunk message. Worth adding cases for: two consecutive skips, a skip followed by a multi-chunk message, a zero-chunk message adjacent to a skip, and a replay that crosses a batch boundary — these are the shapes most likely to expose an off-by-one in
result_group_index. - Unguarded dict lookup breaks this function's own error-handling convention — see inline suggestion below.
- Positional pairing is inferred from group counts, not verified by identity.
ChunkProcessingResultalready carries amessage: TMessagefield; assertingresult.message is messagewhile pairing would catch a future reassembler regression that silently swapped two same-sized messages (a scenario the current count-only checks wouldn't catch).
Suggestions
- Line 341-342:
"Chunk results exceed staged messages with chunks"carries no numbers (consumed vs. expected group count) — worth adding for debuggability, consistent with the other guards in this function. - Line 303 comment ("Store embedding by source message/chunk location until commit remaps it") is a little misleading — the map itself is never remapped; it's read with the source key throughout, while a separate list is built in storage order.
Strengths
- Transaction semantics are clean: every raise in this function happens before any DB-mutating call, inside a
BEGIN IMMEDIATE/rollback-on-exception transaction — no path leaves partial state. - Exception propagation to the caller is fail-fast, no swallowing (
asyncio.TaskGroup). fail_on_textonControlledExtractoris a well-justified addition for deterministic failure injection under concurrent extraction._replay()realistically mirrors the production importer pattern intools/ingest_email.py.
(Note: add_messages.py's ChunkProcessingResult docstring still references a nonexistent success property — confirmed that file is unchanged by this PR/identical to main, so that's pre-existing and out of scope here, but worth a follow-up cleanup.)
|
This PR conflicts with bmerkle/typeagent-py@7802f0b — bmerkle@7802f0b Only one real conflict, in Verified after merging: Kevin Turcios (@KRRT7) feel free to cherry-pick/merge that commit into this branch, or I'm happy to open it as a fresh PR against |
|
Bernhard Merkle (@bmerkle) why don't you open the PR against this branch so that it resolves it here and attribution is kept properly? |
Summary
Validation