Repository navigation
fix(kb): single-sentence chunks skip subclass metadata (#14467) - #14482
Conversation
Review — APPROVE with one structural itemThe fix is correct and the test discriminates. Verified independently rather than on report:
That last point independently corroborates #14485: the ingest pipeline uses a different chunker entirely. The one item — enrichment is two-sited, and that is the shape of the bug
Collapse the two branches into one assignment converging on a single trailing enrichment call before the Noted, not counted against this PR: the exception-driven Standards clean: no |
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
The red here is inherited from the base — do not "fix" it in this PR
This PR did not cause it. Do not raise Fix in flight: PR #14497 (#14495) extracts the six Related: #14498 records a gap in the ratchet itself — its header says "raising a ceiling must fail" while the test only blocks raises above a frozen baseline, so a shrink can be spent back one compliant-looking PR at a time. |
Closes #14467
Thinking Path
SemanticChunkerBase.chunk_text()has two return paths that produce chunks successfully: the primary boundary-detection path, which calls_enrich_chunks_with_metadata()(→_build_chunk_metadata()→_extra_chunk_metadata()), and the zero/one-sentence fallback via_create_single_sentence_chunk(), which returned directly without ever calling enrichment. Identical input differing only in sentence count therefore produced chunks with different metadata shapes — the short-input chunk silently missingchunk_index,total_chunks,embedding_model,chunking_method, and any subclass-contributed key (optimization_versiononGPUSemanticChunker).Before touching the fix, I mapped the blast radius:
SemanticChunkerBase:AutoBotSemanticChunker(utils/semantic_chunker.py, CPU) andGPUSemanticChunker(utils/semantic_chunker_gpu.py). Both override only_extra_chunk_metadata(),_initialize_model(),_compute_embeddings()— neither overrideschunk_text()in a way that bypasses the shared enrichment call (GPUSemanticChunker.chunk_text()wrapssuper().chunk_text()for perf logging only).AutoBotSemanticChunker._extra_chunk_metadata()addschunking_method="semantic_percentile",percentile_threshold.GPUSemanticChunker._extra_chunk_metadata()addschunking_method="gpu_optimized_semantic",gpu_batch_size,optimization_version="rtx4070_gpu".chunk_text()/chunk.metadata:knowledge_sync_incremental.py(_process_file_with_gpu_chunking→_store_chunks_in_knowledge_base) is the only production caller that both invokeschunk_text()and persists chunk data — but it builds the stored metadata dict itself frombase_metadataplus a handful of fields it computes directly (semantic_score,sentence_count,character_count,chunk_index,total_chunks,gpu_optimized); it never readschunk.metadata, sooptimization_version/chunking_methodwere never persisted to the knowledge base by this caller either way.advanced_rag_optimizer.pyconstructs aGPUSemanticChunkerbut never callschunk_text()on it (used only as an availability flag).structured_ops.pyreads onlychunk.content. The publicchunk_document()LlamaIndex-style entry point (which does surfacechunk.metadata) has no production caller — only a test does.knowledge/pipeline/extractors/semantic_chunker.py, theProcessedChunk-basedChromaDBLoaderpath) uses a completely separate chunker that does not subclassSemanticChunkerBaseat all. This class's metadata — buggy or fixed — never reaches ingested documents through that pipeline. kb: two independent semantic chunkers — the ingest pipeline uses neither the embedding-based one nor its metadata #14485 tracks that as its own finding; cross-linked here so the next reader doesn't re-derive it.chunk_text()(primary, single/zero-sentence fallback, and the exception-driven_fallback_chunking()), I found the exception path has the identical gap. Kept out of scope for this PR and filed as bug(kb): exception-driven _fallback_chunking also bypasses subclass metadata enrichment #14480 to avoid mixing an unreported symptom into this fix.Review follow-up: the first version of this fix made the single/zero-sentence branch call
_enrich_chunks_with_metadata()itself, leavingchunk_text()with two separate call sites that happened to agree — the same "two paths, each independently responsible for remembering the same step" shape that produced #14467. Collapsed both branches to only assignchunks, converging on one trailing_enrich_chunks_with_metadata()call before the coherence-logging block, so a future branch added to this function cannot reintroduce the defect by omission.Scope note: after this change,
chunk_text()is single-sited for two of its three return paths (the primary path and the single/zero-sentence fallback). The third — the exception-driven_fallback_chunking()path reached via theexceptblock — still builds its own metadata dict in_create_fallback_chunk()and bypasses_build_chunk_metadata()entirely. That gap is unchanged by this PR and is tracked separately as #14480.What Changed
autobot-backend/utils/semantic_chunker_base.py:_create_single_sentence_chunk()no longer accepts/returns the caller-suppliedmetadataas the chunk's final metadata. It now only sets the chunk-shape markers (single_sentence, plusempty_inputwhen the sentence list is empty).chunk_text()'s two success branches (single/zero-sentence and boundary-detection) now only assignchunks; a single trailingself._enrich_chunks_with_metadata(chunks, metadata)call runs before the existing coherence-logging block, for both. The exception-driven_fallback_chunking()call in theexceptblock is untouched (tracked as bug(kb): exception-driven _fallback_chunking also bypasses subclass metadata enrichment #14480).autobot-backend/utils/semantic_chunker_gpu_test.py: addedTestShortInputChunksCarryTheSameMetadataAsLongInput, driving the realGPUSemanticChunker.chunk_text()(not_create_single_sentence_chunk()/_build_chunk_metadata()directly) with multi-sentence, single-sentence, and empty (filtered-to-zero-sentence) input, asserting the same subclass/base metadata key set (chunk_index,total_chunks,source_metadata,embedding_model,chunking_method,gpu_batch_size,optimization_version) is present on all three.Verification
python3 -m py_compileon both changed files — syntax clean.AssertionError: [single] missing subclass metadata keys: {'chunking_method', 'optimization_version', 'embedding_model', 'source_metadata', 'chunk_index', 'total_chunks'} (got ['single_sentence']).PASS: single-sentence and empty-input chunks carry the same subclass metadata keys as multi-sentence chunks.autobot-backend/utils/semantic_chunker_gpu_test.pyhas zero diff between the two fix commits; onlysemantic_chunker_base.pychanged.autobot-backend/utils/semantic_chunker_gpu_test.py) against the real repo modules, per project policy of not executing repo code locally.Model Used
Claude Sonnet 5 (model ID: claude-sonnet-5)