feat(cpp): text extraction and chunking - #2822
Conversation
The C++ runtime could not turn a document into retrieval-ready text, so a native agent had nothing to index and any splitter a consumer hand-rolled produced chunks incomparable to the Python SDK's. gaia::chunkFile() extracts plain text, Markdown, and source files and splits them into sentence-aware chunks. splitTextIntoChunks is a port of RAGSDK._split_text_into_chunks, including the section/paragraph fallback, sentence-level splitting of oversized paragraphs, and the word-boundary overlap trim. Code-point counting, str.isspace() and str.isupper() semantics are matched so non-English documents cut at the same places; a fixture test replays chunks generated by the Python implementation and requires byte-identical output. PDF/DOCX/XLSX/PPTX need a document-parsing backend the native binary deliberately does not link. extractFile() refuses them by name and points at registerExtractor(); directories and binary content are refused too, so extraction can never return silently-empty text.
Registering an extractor mutates a process-global registry with no removal hook, so the reuse test failed under --gtest_repeat/--gtest_shuffle; each run now claims its own extension. Adds cases for a throwing extractor, blank-run input, zero overlap, and a fixture pair that straddles the sections<=3 branch — the highest-blast-radius decision in the port and previously only covered incidentally. A Python guard pins cpp/src/unicode_upper_ranges.inc to str.isupper(): the table is what keeps non-ASCII headings cutting at the same lines in both runtimes, and the C++ fixtures can only see the code points they happen to contain. The fixture regeneration recipe now reads bytes rather than text, so a CR in a future fixture cannot bake in a contract the binary-reading extractor can never match.
|
Verdict: Approve This adds a native C++ text-extraction + semantic-chunker ( The one thing worth a follow-up (non-blocking): the extractor's UTF-8 check is more lenient than Python's strict decoder, so a handful of malformed byte sequences would be kept as UTF-8 here but decoded as latin-1 by Python — a narrow, adversarial-input gap rather than something real documents hit. Details below. Real-world evidenceN/A — this is a native C++ library with no GAIA Agent-UI / CLI / MCP / HTTP surface; its proof is the bundled gtest suite ( 🔍 Technical detailsParity spot-checks against
🟢 Minor — UTF-8 validation is more lenient than Python's decoder ( Strengths:
|
The Windows job checked the fixtures out with translated line endings, so the documents no longer matched the chunks committed alongside them and the parity test failed. Both runtimes still agree on CRLF input — verified by rerunning the Python splitter against a CRLF copy — so the contract was intact and only the fixture bytes moved. Marks the fixture directory -text, next to the existing rule that keeps the image fixtures from being mangled the same way, and records the line endings each case was generated from so a future translation fails with that reason rather than an unreadable chunk diff. parity_unicode.md carries CRLF on purpose, so the flag is per-case rather than a blanket "no CR" assertion.
|
Windows was red on the first push. The cause was not a splitter bug: the runner checks out with I ruled out a parity bug before fixing it — converted a fixture to CRLF locally, reran the Python splitter against that same CRLF copy, and got byte-identical chunks from both runtimes at every chunk size. Both sides read binary and keep Fixed by pinning the fixture directory Worth carrying into #2798 ( |
Both remaining red checks are infrastructure, not this diffYour Windows C++ build fix worked —
Do not spend any more time on either. If you have already pushed the CRLF fix, you are done — please report what the Windows build failure actually was so it is captured for #2798, which has to handle the same CRLF class of bug in the SKILL.md parser. |
|
Windows is green after the The one remaining red check is It fails in the "Configure and build integration tests" step, before any code from this PR is compiled. It is also intermittent rather than persistent here — the same job passed on the previous push to this branch and failed on this one with no change to anything it touches. |
The C++ runtime could not turn a document into retrieval-ready text, so a native agent had nothing to index and any splitter a consumer hand-rolled produced chunks incomparable to the Python SDK's.
gaia::chunkFile()now extracts plain text, Markdown, and source files and splits them into sentence-aware chunks whose boundaries matchRAGSDK._split_text_into_chunksbyte-for-byte — an index built by one runtime is directly comparable to one built by the other. PDF, DOCX, XLSX and PPTX stay out of scope and are refused by name; a silent empty extraction would produce an agent answering confidently from nothing.Getting parity right turned out to hinge on Unicode: a differential run against the Python implementation found real mismatches on documents with accented capitals, because Python treats
Üas a section-title start and an ASCII check does not. Hence the generatedstr.isupper()table, guarded by a Python test so it cannot silently drift.Test plan
cmake -B cpp/build -S cpp -DCMAKE_BUILD_TYPE=Release && cmake --build cpp/build -jctest --test-dir cpp/build --output-on-failure— 485/485 pass, 22 of them new:ChunkingTest.ChunkBoundariesMatchPythonRagSdkreplays 11 fixture cases over 6 documents (headers, paragraph fallback, both sides of thesections <= 3branch, title-line heuristic, non-ASCII capitals, NBSP, CRLF, zero overlap) against chunks generated by the Python splitterpytest tests/unit/test_cpp_unicode_table.py— fails if the uppercase table drifts from this interpreter'sstr.isupper()cpp/build/tests_mock --gtest_repeat=3 --gtest_shuffle— clean, so the process-global extractor registry does not leak between runsextractFile("x.pdf")throws naming the type, the supported set, and theregisterExtractor()hook; a directory or binary file is refused rather than extracted as empty textcmake --installshipsgaia/chunking.hand a consumer translation unit compiles and links against itBeyond the committed tests, the port was checked against the Python implementation on ~1,700 generated documents (random headings, rules, abbreviations, mixed scripts, Unicode whitespace, CRLF, chunk sizes 1–500, overlaps 0–100) plus a set of adversarial shapes — zero divergences.
Closes #2795