跳转到主要内容

概览

文档摄取由 src/transcode/ 中的文档转码器处理 —— document.hpp / document.cpp。它把一份上传的 PDF 或电子表格分解为一个有序的 LLM 可消费片段列表(文本和/或图像),策略为**“文本优先,图像(及 OCR)兜底”**。概念性走查参见文件处理
文档转码器把上传的文档拆解为文本与图像片段,供具备视觉能力的 LLM 在对话轮次中读取。(上传接线以当前 HTTP 层为准核实。)

支持的格式与构建开关

格式由 magic bytes 嗅探得出(detect_format),再路由:
格式CMake 开关默认备注
PDFPDFiumMIROBODY_ENABLE_PDF关闭逐页文本层;扫描页 → 图像
.xlsxxlnt(找到 xlnt 时自动)存在 xlnt 处开启每个工作表一张 Markdown 表格
.xls内置的 libxlsMIROBODY_ENABLE_XLS关闭旧版 BIFF
.csv内建(RFC 4180)始终单张 Markdown 表格
OCRTesseract + LeptonicaMIROBODY_ENABLE_OCR关闭需要 MIROBODY_ENABLE_PDF
当某开关关闭时,格式仍会被嗅探,但 process() 会抛出 DocumentError("... not built")
cmake -DMIROBODY_ENABLE_PDF=ON -DMIROBODY_ENABLE_OCR=ON -DMIROBODY_ENABLE_XLS=ON ...

API

namespace mirobody { namespace document {

enum class Format { Unknown, Pdf, Xlsx, Xls, Csv };

struct Part {
    enum class Kind { Text, Image };
    Kind              kind = Kind::Text;
    std::string       text;    // Text: UTF-8 / Markdown
    image::Transcoded image;   // Image: already vision-compliant
    int               page = 0;// 1-based source page / sheet
};

struct Document { Format format; std::vector<Part> parts; };

struct Options {
    image::Limits image_limits = image::QwenLimits;  // Qwen / Gemini / Gpt presets
    int  raster_dpi        = 150;   // density for a rasterized scanned page
    int  pdf_text_threshold = 8;    // < this many chars on a page => treat as a scan
    bool ocr_enabled       = true;  // honored only in a MIROBODY_ENABLE_OCR build
    std::string ocr_lang   = "eng"; // e.g. "eng+chi_sim"
    std::string ocr_datapath;       // dir with <lang>.traineddata; "" => TESSDATA_PREFIX
    std::size_t max_pages  = 0;     // 0 = no cap
};

class Transcoder {
public:
    explicit Transcoder(Options opts = Options());
    Document process(const std::string& input) const;         // throws DocumentError
    static std::string to_markdown(const Document& doc);       // parts -> one Markdown blob
    static Format detect_format(const std::string& input);     // cheap magic-byte sniff
};

}}

各路径产出什么

  • PDF,文本页 → 一个带提取出的文本层的 Text 片段。
  • PDF,扫描页(文本低于 pdf_text_threshold)→ 以 raster_dpi 栅格化,编码为 PNG,经 image::Transcoder 处理以适配 image_limits,作为 Image 片段发出。在 MIROBODY_ENABLE_OCR 构建中,同一张栅格图还会被 OCR,识别出的文本追加为一个 Text 片段。
  • .xlsx / .xls → 每个工作表一个 Text 片段,一张 GitHub 风格的 Markdown 表格(## <sheet title> + 表格)。绝不产生图像。
  • .csv → 单个 Text 片段(RFC 4180 解析 → Markdown 表格;剥离 UTF-8 BOM)。
to_markdown() 把一个 Document 展平:文本片段内联,图像片段作为一行 ![page N image](...) 占位符。
max_pages 为页 / 工作表设界;触发时会追加一条尾注 “(remaining … omitted: max_pages reached)“,从而不会有内容被悄悄丢弃。

线程(PDF)

PDFium 的库初始化/清理是进程全局的且非线程安全,且一份已加载的文档不可被并发访问。PDF 路径把所有 PDFium 工作串行化在一个内部互斥锁之后,并持有那次一次性的全局初始化,因此 Transcoder 可从多个线程安全调用 —— 只是在处理一份 PDF 时,PDF 工作并不并行。CSV / xlsx 无此约束。

试一试 —— document CLI

cli/document.cpp 工具(当 MIROBODY_BUILD_TOOLS 为 ON 时构建)端到端演练转码器:
document extract <in> [<out.md>] [--target qwen|gemini|gpt] [--dpi N] \
                      [--no-ocr] [--ocr-lang L] [--ocr-data DIR] [--max-pages N]
# a scanned PDF, Gemini vision preset, English+Simplified-Chinese OCR
./build/cli/document extract report.pdf out.md --target gemini --ocr-lang eng+chi_sim
图像片段(栅格化的扫描页)写在 <out> 旁边;文本片段落进 Markdown。--target 为栅格化页面挑选视觉预设(默认 qwen)。

单位

从文档中提取的数值可用单位引擎(src/fhir/units/)归一化到 UCUM —— 参见数据映射

测试

tests/transcode/document_test.cpptests/transcode/file_test.cpptests/transcode/image_test.cpp,以及 tests/fhir/units_test.cpp(Catch2)。运行转码器子集:
./build/tests/mirobody_tests "[transcode]"
把提取出的数值作为带编码的 Observation 写回 FHIR 存储(文档 → 指标 → FHIR 流水线)尚在进行中 —— 转码器与 UCUM 归一化已上线;术语映射与写回尚在计划中(src/fhir/README.md 的第 3–4 阶段)。(verify)

下一步

文件处理(概念)

流水线一览

数据流

上传的定位

数据映射

UCUM 归一化

测试

运行转码器测试