運作原理

MarkItDown 的 convert() 是「把檔案交給『對的』converter」的調度器

MarkItDown 的核心不是「讀懂檔案」,而是「猜測檔案是哪種格式,然後交給對應的 converter」。 所有格式都遵循同一個 DocumentConverter 協定(accepts() + convert()), 由 MarkItDown._convert() 依優先序逐一嘗試,直到有人成功。

調度管線五步驟

1. 建立 StreamInfo 猜測清單(線索卡)

StreamInfo 是描述「這個檔案看起來是什麼」的資料結構,欄位含 mimetype / extension / charset / filename / local_path / url。 來源可以是副檔名、Content-Type header、Content-Disposition 的 filename、或 URL 的副檔名。

StreamInfo(
    mimetype="application/pdf",
    extension=".pdf",
    filename="report.pdf",
    local_path="report.pdf",
)

2. magika 讀位元組補強猜測

_get_stream_info_guesses() 會再呼叫 magika(Google 的內容型別偵測模型, 純本機、零網路)直接讀檔案位元組猜型別,並用 charset-normalizer 猜文字編碼。 如果 magika 的猜測與線索衝突(例如副檔名說 .txt 但內容是 HTML), 就會產生多個 guess,依序嘗試。

3. 依 priority 排序 converters

每個 converter 註冊時帶一個 priority越小越先試。 內建 converter 大多為 0.0(特定格式);PlainTextConverter / HtmlConverter / ZipConverter10.0(通用兜底,放最後)。排序是穩定排序,同優先級保持註冊順序、後註冊者在前。

4. 依序問 accepts(),接受就 convert()

對每個 guess × 每個 converter,先問 converter.accepts(file_stream, stream_info) (通常用 mimetype / extension 判斷),接受後呼叫 converter.convert(...) 得到 Markdown。 任一 converter 拋例外時不會立刻失敗——被記錄為 FailedConversionAttempt,繼續試下一個。

5. 輸出標準化 + 錯誤聚合

成功後會把輸出做標準化:去掉每行行尾空白、把連續 3+ 個換行壓成 2 個。 全部 converter 都失敗時,若有失敗紀錄 → 拋 FileConversionException(內含所有嘗試); 完全沒人接受 → 拋 UnsupportedFormatException

圖解
convert("report.pdf")
  └─ 線索:副檔名 .pdf + magika 內容偵測
  └─ guesses = [StreamInfo(.pdf), StreamInfo(magika 猜測)]
  └─ 依 priority 排序 converters:[PDF, DOCX, XLSX, ..., PlainText(10), Html(10), Zip(10)]
  └─ PDF.accepts() → True → PDF.convert() → Markdown 字串
  └─ 標準化輸出 → DocumentConverterResult(markdown=..., title=...)

容錯鏈:converter 失敗不代表整個轉換失敗

這是 MarkItDown 最體貼的設計:某個 converter 在 convert() 中途拋例外時, 該例外被 except Exception 接住、包成 FailedConversionAttempt 存進清單, file_streamseek 回原位置,繼續試下一個 converter。 最後若全軍覆沒才統一拋 FileConversionException(attempts=...),方便你一次看到所有失敗原因。

English original — the fail-safe loop
for stream_info in stream_info_guesses + [StreamInfo()]:
    for converter_registration in sorted_registrations:
        converter = converter_registration.converter
        if converter.accepts(file_stream, stream_info, **_kwargs):
            try:
                res = converter.convert(file_stream, stream_info, **_kwargs)
            except Exception:
                failed_attempts.append(FailedConversionAttempt(
                    converter=converter, exc_info=sys.exc_info()))
            finally:
                file_stream.seek(cur_pos)
        if res is not None:
            res.text_content = normalize(res.text_content)
            return res
if failed_attempts:
    raise FileConversionException(attempts=failed_attempts)
raise UnsupportedFormatException(...)

依賴式輸出:圖片與音訊

圖片與音訊的輸出「依賴本機工具或 LLM」:

為什麼選 Markdown?

Markdown 非常接近純文字、token 效率高,同時又能表達標題、清單、表格、連結等結構; 主流 LLM(如 GPT-4o)原生「會講」Markdown。這是它存在的根本理由。