📝 docs(markitdown): clarify universal chunk drawer routing
State that filename and card clicks open the chunk drawer for every file,
while unsupported formats still receive the explanatory empty state.
📝 docs(knowledge): document universal chunk drawer
📝 docs(markitdown): restore chunk provenance sentence
Restore the sentence subject after the bounded-table explanation so the chunk
viewer documentation reads correctly.
📝 docs(knowledge): document bounded chunk drawer tables
Explain bounded Markdown table pages, capability-aware Office empty states,
and the provenance-only chunk metadata contract.
📝 docs(knowledge): document chunk drawer pagination
Document Markdown-rendered per-chunk navigation, Office zero-chunk parsing, and the re-parse requirement for existing malformed tables.
📝 docs(markitdown): per-file MarkItDown icon + content-only chunk viewer
Document the new MarkItDown file-row action (re-parse via MarkItDown + open
converted content) and that the chunk drawer is now a content-only viewer —
Office documents no longer preview via Office Online, and clicking an Office
file's name opens the converted-content viewer once chunked.
📝 docs(markitdown): document in-UI re-parse action and chunk provenance badge
The Verify section now describes the portal affordances instead of implying
provenance is only visible via raw metadata: the Chunks tag opens the drawer,
each chunk shows a MarkItDown/LangChain provenance badge, clicking a chunk
opens a detail modal with converted_by/type, and historical files can be
re-chunked via the file row's "Re-parse chunks" action (which routes through
the sidecar once MARKITDOWN_SERVICE_URL is set).
📝 docs(markitdown): fix LLM base URL wording, drop output-equivalence claim, distinguish image description from OCR
Three wording corrections after d6391d8:
- LLM_BASE_URL: "custom or non-OpenAI endpoint that exposes an
OpenAI-compatible API" (3 spots: env example, feature table, build-time
env block). The old "non-OpenAI-compatible endpoint" was self-contradictory
— the openai SDK only talks to OpenAI-compatible APIs. Confirmed against
server.py docstring which already calls it an "OpenAI-compatible endpoint".
- Alternative images: drop "handles every native-text format identically —
verified by importing every converter". Converter imports prove
availability, not output equivalence. Replaced with "supports the same
native-text input formats" plus a fixture-validation note.
- What-it-adds image row: "EXIF metadata, plus an LLM-generated description
when configured; OCR is available separately through supported Azure paths"
— separates the built-in ImageConverter's description from OCR.
📝 docs(markitdown): clarify LLM base URL conditionality and split captioning from OCR plugin
Three wording corrections after the structural redesign:
- markitdown.env comment: MARKITDOWN_LLM_BASE_URL is set only for a
non-OpenAI-compatible endpoint; API_KEY + MODEL are the required pair.
- Conditional feature groups: split the bundled "LLM vision" row into
standalone image captioning (built-in ImageConverter, no plugin) and the
OCR plugin (needs ENABLE_PLUGINS + INSTALL_OCR_PLUGIN). Standalone
captioning never required ENABLE_PLUGINS.
- markitdown-ocr plugin scope: it registers converters for PDF/DOCX/PPTX/XLSX
only, not standalone PNG/JPEG. Confirmed against upstream pyproject.toml
(v0.1.7): description lists PDF, DOCX, PPTX, XLSX; no standalone image
format in keywords. Standalone images go through the built-in
ImageConverter, which uses the same llm_client without plugins.
📝 docs(markitdown): redesign page structure — unified Quick start, env_file compose
Consolidates the two parallel "Run" flows (prebuilt + build) that had grown
to duplicate topology, verify, and ChatHub-wiring sections into one Quick
start with a shared markitdown.env and two options (A pull, B build). Moves
the build-path docker-compose.yml to env_file: so it matches the prebuilt
one and ChatHub's component-scoped env convention — no inline environment:
block remains. Gathers all ChatHub-side wiring into a single Connect ChatHub
section. No factual changes; same HTTP contract, same OCR coverage, same
Dockerfile/requirements/server.py.
📝 docs(markitdown): apply review corrections — plugin enablement, recipe scope, Azure boundary, output, reproducibility
Five review findings, all verified against the markitdown v0.1.7 source and
the running production image before applying.
1. Plugin enablement needs MARKITDOWN_ENABLE_PLUGINS=true, not just the
build arg. MarkItDown loads entry points only when enable_plugins=True,
and the sidecar defaults MARKITDOWN_ENABLE_PLUGINS to false. Building
with INSTALL_OCR_PLUGIN=true without the runtime flag leaves the
markitdown-ocr entry point unregistered. The full LLM-OCR path needs all
three: INSTALL_OCR_PLUGIN=true + MARKITDOWN_ENABLE_PLUGINS=true +
MARKITDOWN_LLM_*. Corrected in Build-time only and OCR for scanned
documents and pictures.
2. INSTALL_OCR_PLUGIN is Debian-recipe only. The current prebuilt Wolfi
image does not define or consume it, so "off by default on every recipe"
was too broad. The component table now has separate Debian and Wolfi
columns, and the intro notes the Wolfi recipe must be extended before
the arg can be used. Verified from the image's docker history.
3. Azure Document Intelligence is not equivalent to markitdown-ocr for
embedded Office images. In markitdown v0.1.7 the
DocumentIntelligenceFileType enum marks DOCX/PPTX/XLSX/HTML as
"# No OCR" and PDF/JPEG/PNG/BMP/TIFF as "# OCR", so Azure runs Office
formats without OCRing their embedded images. Reworded the Azure path
to state the boundary; noted Content Understanding is a separate path,
not identical to Document Intelligence. Verified from
converters/_doc_intel_converter.py.
4. Scanned PDFs return empty Markdown, not EXIF. EXIF metadata is an
image-input concept (exiftool converter); PDFs go through text
extractors which return empty for image-only pages. Distinguished the
two cases with the verified {"chars":0,"markdown":"","title":null}
output for an image-only PDF.
5. Reproducibility and compatibility claims softened. Removed "unpinning
produces a working image", "converts files identically", and "pin for
byte-for-byte output" — upgrades may change behaviour and top-level pins
do not guarantee reproducibility. Replacement states upgrades are
supported but require HTTP-contract tests, conversion fixtures, and a
fresh CVE scan; true reproducibility needs a full lock, pinned
base-image digest, pinned upstream revision, and controlled build
environment.
Confirmed-correct conclusions left in place: native-text completeness, no
LLM/Azure needed for basic conversion, text-PDFs-with-images supported,
image contents ignorable, markitdown-ocr optional not missing, PyMuPDF is a
renderer not the OCR engine.
📝 docs(markitdown): correct OCR architecture — LLM vision is the engine, PyMuPDF only renders
The page conflated PyMuPDF/fitz with the OCR capability, implying the library
itself does the OCR. Per the markitdown-ocr source at v0.1.7, PyMuPDF is only
a page-rendering dependency (and only a fallback after pdfplumber); the actual
OCR engine is the OpenAI-compatible vision LLM configured at runtime via
MARKITDOWN_LLM_*. The package's own description is "Extracts text from images
in PDF, DOCX, PPTX, and XLSX via LLM Vision," and its OCR service class is
LLMVisionOCRService, which base64-encodes a page image and sends it to a
chat.completions.create vision call.
Practical consequence: building with INSTALL_OCR_PLUGIN=true but leaving
MARKITDOWN_LLM_* unset produces no OCR — there is no engine to call. The
plugin needs both the build arg and the runtime LLM config to function.
- Size-table note: reworded from "pulls PyMuPDF" to "adds the markitdown-ocr
plugin, including its PyMuPDF rendering dependency."
- Component table OCR row: "the LLM vision model is the OCR engine, PyMuPDF
only renders pages."
- Dockerfile comment: same correction inside the code block.
- Build-time only section: state explicitly that the plugin needs BOTH the
build arg and runtime MARKITDOWN_LLM_*, and that standalone image
captioning works via MARKITDOWN_LLM_* alone (no plugin needed).
- Plugin probe: import markitdown_ocr, not pymupdf — PyMuPDF is a transitive
rendering dep and is the wrong thing to test for plugin presence.
Verified the probe against an image built without the plugin: the import
traceback is the expected "not baked in" signal.
📝 docs(markitdown): reframe OCR/picture handling as optional, not a gap
The page framed scanned-PDF / picture OCR as a "caveat" and a "gap" of
alternative images, which reads as a deficiency. It is an optional feature:
off by default on every recipe (Debian ARG default, Compose args default),
and the only input class any default build does not handle. Deployments that
never ingest scanned or picture-only input — the common case — can ignore
it entirely and the sidecar is complete as built.
- Reframe the size-table paragraph: the OCR plugin is "an optional feature,
not something any default build ships," with a forward link instead of
warning about a shared gap.
- Rename "What's not in alternative images (and the default Debian build)" to
"Alternative images (Chainguard Wolfi, multi-stage, prebuilt, CVE-pinned)".
Drop the caveat/gap language; keep the component-presence table but reword
the OCR row as "optional — off by default on every recipe" and mark the
runtime opt-in rows (image captioning, Azure OCR) accordingly.
- Rename "Scanned documents" to "OCR for scanned documents and pictures"
(matches the two intro link anchors) and lead with "this is an optional
capability: if your deployment never ingests scanned or picture-only input,
you can ignore this section entirely and the sidecar is complete as built."
- Reword "Build-time only" to consistent optional framing.
📝 docs(markitdown): correct OCR-plugin attribution — it is opt-in everywhere
The previous round implied the OCR plugin (markitdown-ocr / PyMuPDF) ships in
the documented Debian build but is absent from alternative recipes. The
Dockerfile ARG default and the Compose args value are both
INSTALL_OCR_PLUGIN=false, so the wiki's own `docker compose up -d --build`
omits the plugin too. A default-args alternative image (e.g.
img.aksg.net/markitdown/markitdown:0.1.7) is missing nothing the default
Debian build has — only an explicit INSTALL_OCR_PLUGIN=true rebuild adds the
plugin on either side.
- Fix the size table: ~1.07 GB is the default-args Debian build, the
INSTALL_OCR_PLUGIN=true variant is ~80 MB heavier (~1.15 GB), and state
explicitly that the plugin is off by default in both the ARG and the Compose.
- Reword "What's not in alternative images" to "What's not in alternative
images (and the default Debian build)" so the gap is correctly attributed to
the opt-in plugin across all recipes, not to the choice of base image. Same
recovery paths (AZURE_DOC_INTEL_ENDPOINT, or INSTALL_OCR_PLUGIN=true rebuild).
📝 docs(markitdown): cover alternative recipes and the OCR-plugin caveat
The page documented one Debian build and one 1.07 GB size, so readers
using a prebuilt / Chainguard-Wolfi / multi-stage image (e.g.
img.aksg.net/markitdown/markitdown:0.1.7, 732 MB, 0 CVE) had no guidance
for what to expect and no explanation for the size gap. Recipes that pin
deps to patched versions for CVE hygiene were also left implicit.
- Replace the single-row size table with a range across recipes, and a
sentence attributing the gap to base image and INSTALL_OCR_PLUGIN.
- Note on requirements.txt that the pins are for reproducibility, not
correctness — the HTTP wire format is stable across these minor
versions, so unpinning for CVE hygiene produces a working image.
Verified against an image that resolved fastapi to 0.141.1, openai
to 1.68.2: every markitdown[all] converter imports, multipart upload
semantics are unchanged.
- Add a "What's not in alternative images" subsection under Run a prebuilt
image, listing what a Chainguard-Wolfi / multi-stage build leaves out:
only the markitdown-ocr plugin (PyMuPDF). Native-text converters, audio,
image captioning and Azure OCR are all present. Scanned-PDF OCR is the
sole gap, with two recovery paths (AZURE_DOC_INTEL_ENDPOINT, or rebuild
with INSTALL_OCR_PLUGIN=true) and a one-line probe for the plugin.
📝 docs(markitdown): split mandatory from optional, add prebuilt-image path
Readers with a built image (e.g. img.aksg.net/markitdown/markitdown:0.1.7)
had to translate the build-path Compose to remove `build:` themselves, and
the env table did not separate the truly-required vars (an API key when the
port is exposed past loopback) from overrides. Mirror ChatHub's
component-scoped env convention: the sidecar's vars go in markitdown.env
loaded with env_file:, ChatHub's own MARKITDOWN_* vars stay in its existing
.env next to its compose, so each component owns its env and the compose
carries no secrets.
- Add a Run a prebuilt image section with the env file template split into
Mandatory / Non-mandatory / Optional feature groups, the prebuilt
Compose, network topology, ChatHub-side wiring and verification steps.
- Restructure Configure it into Mandatory (only MARKITDOWN_API_KEY, gated on
network exposure), Conditional feature groups (set each fully or not at
all), Non-mandatory (built-in defaults), Build-time only
(INSTALL_OCR_PLUGIN is a Docker arg, not an env), and a dedicated
ChatHub-side vars table covering MARKITDOWN_SERVICE_URL/API_KEY/
MAX_FILE_SIZE/TIMEOUT.
No code, runtime or test change; build path and HTTP contract unchanged.
📝 docs(markitdown): harden the sidecar and complete the Compose pass-through
Review fixes for the page's embedded files, applied here since the page is the
sidecar's only home:
- server.py no longer reads the whole upload into memory before checking
MARKITDOWN_MAX_FILE_SIZE. A middleware rejects a declared-oversize body
before the form parser spools it, and /convert reads the spooled file in
1 MiB pieces, refusing once the cap is passed — so the cap now actually
bounds memory, and a body with a lying or absent Content-Length is caught
too.
- The bearer-token check uses secrets.compare_digest over bytes:
constant-time, and immune to the TypeError the str form raises on non-ASCII
input.
- docker-compose.yml now passes through MARKITDOWN_LOG_LEVEL,
MARKITDOWN_LLM_PROMPT and AZURE_CU_ANALYZER_ID, which the Configure-it
table documents but the container never received; the log level also
survives an empty-string value now.
- The HTTP-contract note no longer claims every error falls back to the
built-in loaders: a FILE_TYPE_CHUNKING_RULES entry that pins a format to
markitdown alone surfaces its errors directly, and the sidecar's reason
reaches the surfaced error only when the fallback also fails.
Wire contract unchanged: same endpoints, fields and status codes.
Verified by extracting the four blocks, building the image and driving it
live: csv converts with auth (200); wrong/missing/non-ASCII token all 401; a
500 KB upload over a 100 KB cap 413s from the bounded read; a 2 MB upload
413s from the middleware before parsing; empty upload 400; unknown format
415.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 docs(markitdown): add the sidecar as a supplementary page
The sidecar's build files were removed from the main repository, where they were
Python clutter that no CI workflow built and no ChatHub image shipped. Carry them
here instead, so the sidecar stays rebuildable without a checkout.
MarkItDown-Sidecar.md holds the Dockerfile, requirements.txt, server.py and
docker-compose.yml verbatim, plus how to build, run and update it, the format
table, the configuration reference, the OCR guidance and the security notes.
Two additions the old in-repo README did not have:
- An HTTP contract section. src/libs/markitdown/index.ts depends on the wire
format, and nothing in the ChatHub repository tests the server side — the
client's suite mocks fetch and the chunking suite mocks the client — so either
side can change and every test still passes. Documented as frozen, next to the
code it constrains.
- A note that the format table is the authority for MARKITDOWN_EXTENSIONS in
packages/utils/src/isChunkableFile.ts, which drives the upload picker.
Also re-point Knowledge-Base-and-RAG.md at this page in place of the deleted
docker/markitdown/README.md, including the compose command that no longer
resolves, and add the page to the sidebar after Knowledge Base and RAG.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>