feat(core,parsers): document parsers — PDF/DOCX/PPTX/XLSX/HTML/MD/text/JSON/CSV/YAML (Step 1.3)#57
Merged
Conversation
…t/JSON/CSV/YAML (Step 1.3)
New rag-parsers package (packages/parsers/, v0.1.0) ships ten built-in
Parser implementations plus puremagic-based MIME detection and a
ParserRegistry. The Parser SPI is evolved to return a ParsedDocument
(Document + extracted text + structural Block list with offsets,
heading levels, and parser-specific attributes), so Step 1.5's chunker
can recover structure without re-parsing the source bytes.
Highlights:
- Text-family: PlainTextParser (text/*, BOM-aware UTF-8 with latin-1
fallback), MarkdownParser (markdown-it-py tokens → headings/lists/
code/quotes with line-map offsets), HtmlParser (BeautifulSoup +
stdlib html.parser, drops script/style), JsonParser (validate +
pretty-print + NDJSON), CsvParser (header-aware table_row blocks),
YamlParser (safe_load multi-doc).
- Binary: PdfParser (pypdf, page-per-block with {"page": N}), DocxParser
(python-docx, Heading 1-6 style detection + table rows), PptxParser
(python-pptx, slide title as level-2 heading + body paragraphs +
speaker notes as caption blocks), XlsxParser (openpyxl read-only,
sheet name as heading + row blocks).
- rag_parsers.mime.detect_mime: extension shortcut for deterministic
formats, puremagic content sniff, OOXML ZIP central-directory
disambiguation (word/, ppt/, xl/), caller-hint fallback.
- ParserRegistry + default_registry() with specific-before-generic
ordering; PlainTextParser is the text/* fallback.
- ragctl parse <path> smoke-test command — MIME-detect, select parser,
print summary + first-N blocks with type/level/start. Useful for
design-partner demos before Step 1.10's full ingest API lands.
SPI evolution:
- New types in rag_core: Block (type/text/start/level/attributes),
BlockType (heading/paragraph/list_item/table_row/code/quote/caption/
other), ParsedDocument (document + text + blocks + detected_mime).
- Parser.parse return type changes from Document to ParsedDocument;
NoopParser updated to emit a single paragraph block.
- Schemas regenerated: dist/schemas/Block.json, ParsedDocument.json.
- rag-core 0.7.0 → 0.8.0.
Cross-platform: every parser library is pure-Python or wheel-distributed
(puremagic, markdown-it-py, beautifulsoup4 + stdlib html.parser, PyYAML,
pypdf, python-docx, python-pptx, openpyxl). No libmagic / lxml system
deps — matches the standing Windows/macOS/Linux-without-WSL constraint.
Tests:
- 35 unit tests under tests/parsers/ (MIME detection, text-family
parsers, binary parsers with in-memory fixture generation, registry
resolution).
- 6 new ragctl tests for the parse command (typer CliRunner).
- Contract tests (tests/contract/test_parser.py) extended for the new
ParsedDocument return shape, empty-input handling, and block-offset
invariants. All 148 contract tests pass.
Documentation:
- docs/reference/parsers.md — public + technical reference for the
Parser SPI, supported MIMEs, usage, error handling, internals,
extension points.
- docs/architecture/parsers.md — block taxonomy rationale, MIME
resolution strategy, policy boundary (parsers are upstream of
PolicyEngine; enforcement is in Step 1.10's pipeline), hot-path
discipline, related ADRs.
- docs/README.md updated with both new entries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 24, 2026
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Step 1.3 — ten built-in document parsers + MIME detection. The new
rag-parserspackage (packages/parsers/, v0.1.0) plugs into the ingest pipeline between connectors (Step 1.2) and the chunker (Step 1.5).The
ParserSPI is evolved to returnParsedDocument(the embeddedDocumentplus extracted text and a list of structuralBlockrecords) so Step 1.5 can recover structure without re-parsing source bytes. This is a load-bearing decision; reviewer attention onBlocktaxonomy welcome.Highlights
PlainTextParser,MarkdownParser(markdown-it-py),HtmlParser(BeautifulSoup + stdlibhtml.parser),JsonParser,CsvParser,YamlParser(PyYAMLsafe_load).PdfParser(pypdf),DocxParser(python-docx, Heading 1-6 style detection + table rows),PptxParser(python-pptx, slide title as level-2 heading + body + speaker notes),XlsxParser(openpyxl read-only).rag_parsers.mime.detect_mime(): extension shortcut → puremagic content sniff → OOXML ZIP central-directory disambiguation → caller-hint fallback. Nolibmagicsystem dep.ParserRegistry+default_registry()with first-match-wins ordering (specific MIMEs before fallbacks).ragctl parse <path>— smoke-test command: MIME-detect, parse, print summary + first-N blocks. Useful for design-partner demos before Step 1.10's full ingest API.SPI evolution
New types in
rag_core:Block—type/text/start(offset intoParsedDocument.text) /level(1-6 for headings) /attributes(e.g.{\"page\": 3},{\"sheet\": \"Q1\", \"row\": 5}).BlockType— 8-value enum: heading, paragraph, list_item, table_row, code, quote, caption, other.ParsedDocument— document + text + blocks + detected_mime.Parser.parse(...)return type changes fromDocumenttoParsedDocument.NoopParsermigrated. Contract tests extended for the new shape, empty-input handling, and block-offset invariants. JSON schemas regenerated.rag-core0.7.0 → 0.8.0.Cross-platform
Per the standing Windows/macOS/Linux-without-WSL constraint, every parser library is pure-Python or wheel-distributed: puremagic, markdown-it-py, beautifulsoup4 + stdlib
html.parser, PyYAML, pypdf, python-docx, python-pptx, openpyxl. Nolibmagic/lxml/ system OCR dependencies in this PR (OCR lands in Step 1.4).Tests
tests/parsers/(MIME detection, text-family, binary parsers via in-memory fixture generation, registry resolution).parsecommand (typerCliRunner).task lintclean: ruff, ruff format, mypy --strict, RAG001 logging gate.Policy boundary
Like connectors, parsers are upstream of
PolicyEngine. The PDP can't enforce ACL/PII on bytes that haven't become Chunks yet — enforcement stays in the Step 1.10 pipeline (per-document quota check before parse; per-chunk ACL + PII after chunk). The parsers package depends only onrag-core, not onrag-policy.Documentation
docs/README.mdupdated with both new entries.Known pre-existing issue
`uv run ragctl parse ` currently fails to import workspace packages outside pytest, because uv-generated editable-install
.pthfiles in `.venv/lib/python3.12/site-packages/editable_impl_rag.pth` lack trailing newlines, so Python's `site.py` silently skips them. Same failure on `main`: `uv run ragctl version` reproduces it. Tests work because pytest's `pythonpath` config adds each `packages//src` explicitly. This isn't introduced by Step 1.3 — flagged as a separate spawned task to investigate.Tracker
TRACKER.mdStep 1.3 flipped ⏳ → 🚧; Next action advanced to Step 1.4 (OCR). The Phase 1 milestone counts will be reconciled when #56 (the chore tracker-sync for Step 1.2) merges and this PR rebases / merges on top.Test plan
🤖 Generated with Claude Code