Skip to content

fix(retain): merge JSON arrays in append mode to preserve conversation-aware chunking - #2412

Merged
nicoloboschi merged 4 commits into
vectorize-io:mainfrom
qxxaa:fix(retain)-merge-JSON-arrays-in-append-mode-to-preserve-conversation-aware-chunking
Jun 26, 2026
Merged

fix(retain): merge JSON arrays in append mode to preserve conversation-aware chunking#2412
nicoloboschi merged 4 commits into
vectorize-io:mainfrom
qxxaa:fix(retain)-merge-JSON-arrays-in-append-mode-to-preserve-conversation-aware-chunking

Conversation

@qxxaa

@qxxaa qxxaa commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Problem

When update_mode=append is used with conversation-format content (flat JSON arrays of {role, content, timestamp} dicts), original_text becomes invalid JSON after the second retain cycle.

In _streaming_retain_batch, combined_content is built with:

combined_content = "\n".join([c.get("content", "") for c in contents_dicts])

For conversation content, this produces [...]\n[...] - two JSON arrays joined by a newline. This is neither valid JSON nor valid JSONL (lines are arrays, not dicts).

On subsequent append cycles, chunk_text() fails to parse the corrupted original_text. _chunk_jsonl() also rejects it. The text falls through to RecursiveCharacterTextSplitter, which splits on sentence boundaries (. ) inside message content with no awareness of conversation turn structure. Chunks begin mid-sentence without speaker attribution, and the extraction LLM misattributes statements.

Fix

In orchestrator.py, in _streaming_retain_batch, after the append-mode block assembles contents_dicts = [existing_content, *contents_dicts]: detect when all content items are JSON arrays of dicts, parse them, merge into a single flat array, and re-serialize.

Non-conversation content (plain text, JSONL, single JSON objects) is unaffected - the isinstance check fails and the block is skipped.

Tests

  • New chunking and integration tests in test_chunking.py and test_retain_append_mode.py verify both the corrupted path (regression guard) and the merged path (correct routing through _chunk_conversation).
  • test_newline_joined_json_arrays_bypass_conversation_chunking is intentionally a regression document (proves the broken path exists), not a behavioural assertion. The second test (test_merged_json_array_routes_to_conversation_chunking) validates the correct routing.
  • max_chars=120 in the routing test is budget-sensitive but intentionally tight to force a multi-chunk split with short test messages.

Closes #2409

qxxaa added 4 commits June 25, 2026 21:51
…n-aware chunking

When update_mode=append prepends existing document text as a second
content item, combined_content is built with "\n".join(...). For
conversation-format content (flat JSON arrays of message dicts), this
produces "[...]\n[...]" which is not valid JSON.

On subsequent append cycles, chunk_text() fails to parse the corrupted
original_text. _chunk_jsonl() also rejects it (lines are arrays, not
dicts). The text falls through to RecursiveCharacterTextSplitter, which
splits on sentence boundaries with no awareness of conversation turn
structure. This produces chunks that begin mid-sentence without speaker
attribution, causing the extraction LLM to misattribute statements.

Fix: after the append-mode block assembles contents_dicts with the
existing and new content items, detect when all items are JSON arrays
of dicts and merge them into a single flat array. Non-conversation
content (plain text, JSONL) is unaffected.

close vectorize-io#2409
Add tests for chunking newline-joined and merged JSON arrays.
This test ensures that appending conversation arrays maintains the original_text as a valid flat JSON array after multiple append cycles, preventing degradation of the data structure.

@nicoloboschi nicoloboschi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Confirmed the bug and traced it through the code. Forward fix is correct: merging the JSON arrays keeps original_text valid so subsequent appends route back through _chunk_conversation. Tests pass locally. Healing of pre-existing corrupted docs is intentionally out of scope.

@nicoloboschi
nicoloboschi merged commit 78d32cd into vectorize-io:main Jun 26, 2026
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.

Bug: append mode corrupts original_text, breaking conversation-aware chunking on subsequent retains

2 participants