IMPLEMENTATION PLAN · REVIEW DRAFT

Markdown 表格统一识别与切块实施计划

面向 zleap-sag 的 Markdown、CSV、XLSX 统一语料处理方案:文件表格先由 MarkItDown 转为 Markdown,再通过 TABLE Block 完成表头感知、按行切分、证据引用和持久化。

改造目标:/Users/mac/dev/zleap 参考实现:/Users/mac/dev/zleap_sag 目标包:packages/sag 实施方式:单次完整变更 文档状态:待审阅 基线:23 tests passed

01. 方案结论

表格的标准中间形态确定为 Markdown,不再引入独立的单元格领域模型作为 LLM 输入。Markdown 原文与 CSV/XLSX 转换结果进入同一条结构识别和切块链路。

最终决策: CSV、XLSX 使用 MarkItDown 转换;MarkdownBlockParser 识别 TABLE Block; MarkdownTableChunker 拆成表头与数据行;Assembler 按完整行切分并在每个 Chunk 中重复表头。
统一语料

LLM 读取 Markdown

Chunk 的 contentraw_content 都保留完整 Markdown 表格。

统一切块

一套 TABLE 策略

Markdown、CSV、XLSX 不再分别维护三套表格切块算法。

统一引用

表头和行可追踪

表头只持久化一次,各表格 Chunk 共享表头 Section 引用。

02. 实施基线与已补齐能力

已经具备
  • BlockType.TABLE 已定义。
  • ParsedBlockSectionItemChunkItem 支持类型和 metadata。
  • ArticleSection.type 可存储 TABLE
  • 向量文档已经支持写入 chunk_type
  • Parse、Chunk、Index 阶段边界清晰,可局部扩展。
本次已补齐
  • 增加 MarkItDown 可选依赖和默认 CSV/XLSX converter。
  • MarkdownBlockParser 可生成 TEXT/TABLE Block。
  • Block Router 将 TABLE 交给 MarkdownTableChunker。
  • Assembler 按表头、完整数据行和表格分组组装。
  • Index 复用跨 Chunk 重复的表头 Section。
兼容措施已落实: heading_strict 使用独立 heading-only Block 视图,不直接消费 TABLE 拆分后的 parsed.blocks,因此仍保持一个标题块对应一个 Chunk 的 benchmark 语义。

03. 目标架构

.md   ───────────────────┐
.csv  → MarkItDown ──────┼→ Markdown → TABLE Block → Section → Chunk
.xlsx → MarkItDown ──────┘

这里统一的是“转换后的 Markdown 结构”,不是 Excel 原始单元格结构。样式、合并单元格、公式对象等 不进入本次合同;LLM 只消费 MarkItDown 输出的可读 Markdown。

04. 必须满足的切块不变量

每个连续 Markdown 表格识别为一个独立 TABLE Block。
每个表格生成一个 table_header 和零到多个 table_row Section。
数据行是默认最小原子单元,正常情况下不拆开一行。
表格被切成多个 Chunk 时,每个 Chunk 都包含完整表头。
表格 Chunk 的内容保持合法 Markdown,不转换为去管道符的纯文本。
表格默认不与前后正文、其他表格合并,避免类型和边界丢失。
所有数据行严格保持原始顺序,不丢失、不重复。
重复渲染的表头只保存一个 ArticleSection,多个 Chunk 共享引用。
standardoverlap 支持表格感知;heading_strict 保持旧语义。
除单行本身超限外,最终完整 Markdown Chunk 不超过 max_tokens
超长单行优先保持表格有效,并通过 metadata 显式标记超限。
普通非表格 Markdown 的输出不发生非预期变化。

05. 参考项目逻辑的采用与修正

参考项目行为本次决定原因
MarkItDown 转 Markdown采用满足统一 LLM 语料方向。
Markdown 表格识别为 TABLE Block采用与现有 BlockType 和 pipeline 契约匹配。
表头、数据行分别生成 Section采用支持按行切分和精细引用。
大表拆分时重复表头采用保证每个 Chunk 可被 LLM 独立理解。
单个正则识别整个表格替换使用逐行状态机,避免吞掉表后空白行并改善偏移计算。
小表与前后正文跨 Block 合并不采用混合后 chunk_type 取第一个 Section,表格类型可能丢失。
数据行去掉管道符后作为语义文本不采用用户已确认最终语料应保持 Markdown。
超长单行隐式超过上限显式化保持行完整,但增加 oversized metadata 和测试。
HTML table 作为 raw Section暂不纳入本次范围是 Markdown、CSV、XLSX。

06. 一次性完整实施方案

实施原则: 本功能作为一个完整变更单元一次性交付。依赖接入、格式转换、TABLE Block 识别、 表头感知切块、策略兼容、持久化引用、测试和文档缺一不可;不接受只完成中间某层的部分实现。
1

单一实施单元:打通 CSV/XLSX/Markdown 到 LLM 表格语料的完整链路

下表是同一次代码变更必须覆盖的修改面,不代表多个实施阶段。所有修改在同一工作分支中完成, 统一运行全量测试,达到文末 Definition of Done 后整体交付。

修改面必须同时完成的内容涉及文件
依赖与转换 增加 tables 可选依赖;CSV/XLSX 通过 MarkItDown 转 Markdown; 使用 asyncio.to_thread();支持未 start 的 engine.parse(); 保留显式 converter 优先级,并提供稳定错误码与安装提示。 pyproject.toml
pipeline/converters/markitdown.py
pipeline/parse.py
Block 识别 使用逐行状态机识别 Markdown 表格;保护代码围栏;校验分隔行和列数; 保留准确 start/end 偏移;表格以外内容继续生成 TEXT Block;非法表格安全降级为 TEXT。 modules/load/chunking/parser/markdown.py
Section 构建 新增 Block Router 和 MarkdownTableChunker;生成一个 table_header 与多个 table_row;同表共享 render group;空白行不生成 Section; whitespace merge 仅处理 TEXT。 chunker/table.py
chunker/router.py
chunker/markdown.py
chunker/__init__.py
Chunk 组装 增加 table_with_header 策略;按完整行贪婪切分;每个 Chunk 重复表头; 表格与正文、其他表格隔离;content/raw_content 保持合法 Markdown; 超长单行保留完整并标记 oversized_row modules/load/chunking/assembler/generic.py
模式与版本 standard 启用完整表格链路;overlap 禁止跨 TABLE 边界拼接; heading_strict 使用独立 heading-only 解析路径以保持 benchmark 语义; 表格算法使用独立 implementation version,确保已有表格来源正确重切块。 pipeline/chunk.py
引用与持久化 Index 按全局 section order 复用表头 ArticleSection;校验重复 Section 内容一致; 每个 SourceChunk.references 保持“表头 → 数据行”顺序; SourceReference 使用表头/行级稳定键。 _pipeline_adapters.py
回归、文档与交付 同时补齐 Parser、Chunker、Assembler、CSV/XLSX E2E、模式兼容、Index provenance 测试; 固定普通 Markdown 黄金输出;更新 README、中英文 API、CHANGELOG 和安装示例; 全量单测、lint、format、mypy、wheel 构建全部通过后一次性交付。 tests/unit/test_*table*.py
README.md
api.md / api.zh-CN.md
CHANGELOG.md

统一依赖合同

[project.optional-dependencies]
tables = [
  "markitdown[xlsx]>=0.1.5,<0.2",
  "pandas>=2.2,<3",
]

完整变更的集成判定

  • 任何中间状态都不作为可交付版本;例如“能转换但不能识别 TABLE”视为未完成。
  • Markdown、CSV、XLSX 必须在同一测试运行中证明进入同一表格切块策略。
  • Parse → Chunk → Index 的 Section/Chunk/reference 必须端到端验证,不能只测局部函数。
  • 普通 Markdown 和 heading_strict 回归必须与新表格用例一起通过。
  • 代码、依赖锁、测试、API 文档和 CHANGELOG 在同一变更中提交。

唯一完成条件: 本文第 12 节 Definition of Done 全部满足;否则整个实施单元保持未完成状态。

07. 数据与 metadata 合同

ParsedDocument metadata

{
  "original_format": "xlsx",
  "normalized_format": "markdown",
  "converter": "markitdown",
  "converter_version": "0.1.5"
}

TABLE Block metadata

{
  "table_format": "markdown",
  "header_line_count": 2,
  "row_count": 17,
  "column_count": 5,
  "source_format": "xlsx"
}

Section metadata

{
  "block_type": "TABLE",
  "block_id": "table-3",
  "render_format": "markdown_table",
  "assemble_policy": "table_with_header",
  "role": "table_row",
  "row_index": 7,
  "column_count": 5,
  "start_index": 1234,
  "end_index": 1290
}

Chunk metadata

{
  "chunk_type": "TABLE",
  "assemble_policy": "table_with_header",
  "table_block_id": "table-3",
  "row_start": 1,
  "row_end": 12,
  "row_count": 12,
  "header_repeated": true,
  "oversized_row": false
}
存储结论: 现有 ArticleSection、SourceChunk.references、extra_data 和向量文档 chunk_type 已能承载这些数据, 本次不需要数据库迁移。

08. 表格切块算法

flush 当前正文 Chunk

header = table_header
current_rows = []

for row in table_rows:
    candidate = render_markdown(header, current_rows + [row])

    if tokens(candidate) <= max_tokens:
        current_rows.append(row)
        continue

    if current_rows:
        emit(render_markdown(header, current_rows))
        current_rows = [row]
        continue

    # header + 单行本身已经超限
    emit(
        render_markdown(header, [row]),
        metadata={"oversized_row": True},
    )

if current_rows:
    emit(render_markdown(header, current_rows))
保证

完整性优先

不会为了满足 token 上限而把一条 Markdown 数据行切成多个无效片段。

例外

单行超限

允许 header + 单行超过 max_tokens,但必须可观测、可测试、可统计。

09. Chunk 模式兼容策略

模式表格行为兼容要求
standard 完整 TABLE Block、按行切分、每块重复表头。 新能力主路径
overlap 复用 standard 表格切块,但禁止跨 TABLE 边界追加 overlap。 保证 Markdown 有效
heading_strict 整个标题块保持一个 Chunk,表格只作为标题块内 Markdown 内容。 保持 benchmark 语义

10. 测试矩阵

层级代表用例核心断言
Block Parser标题 + 表格 + 正文Block 顺序为 TEXT/TABLE/TEXT,heading 和偏移正确。
Block Parser代码围栏内伪表格不生成 TABLE。
Block Parser表后空白行不生成空 table_row。
Block Parser非法分隔行完整降级为 TEXT。
Section Builder表头 + 3 行1 header + 3 row,同一 render group。
Assembler小表格独立 TABLE Chunk,不与正文合并。
Assembler长表格多 Chunk;每块重复表头;行精确覆盖原表。
Assembler超长单行行不拆,oversized_row=true。
CSV E2E中文、引号、逗号MarkItDown 后生成 TABLE Block/Chunk。
XLSX E2E两个 Sheet两个独立 TABLE Block,heading 为 Sheet 名。
Pipelineoverlap + table不向表格前后插入跨块文本。
Pipelineheading_strict + table原始标题块仍为一个 Chunk。
Index表格拆成多个 Chunk表头 Section 只保存一次,多个 Chunk 共享 ID。
确定性同一输入执行两次版本、Chunk 内容和引用顺序一致。
回归普通 Markdown既有输出无非预期变化。

建议新增测试文件

tests/unit/test_markdown_table_parser.py tests/unit/test_markdown_table_chunker.py tests/unit/test_table_source_chunk_assembler.py tests/unit/test_markitdown_table_conversion.py tests/unit/test_table_pipeline_e2e.py tests/unit/test_table_index_provenance.py

11. 风险、限制与控制措施

风险影响控制措施
MarkItDown 输出随版本变化 同一文件产生不同 Markdown 和 Chunk。 固定 >=0.1.5,<0.2,记录 converter_version,增加黄金测试。
CSV 单元格包含裸 | 或换行 转换后的 Markdown 列语义可能变化。 加入边界测试;无法可靠识别时保留 TEXT 并产生 warning,不静默丢数据。
正则误识别代码块中的表格 代码内容被错误按表格切分。 逐行状态机先保护 fenced code ranges。
表格与正文合并 chunk_type 丢失,Markdown 上下文混杂。 表格默认独立,进入和离开表格时 flush。
表头跨 Chunk 重复落库 ArticleSection 冗余、引用不一致。 Index 阶段按全局 order_index 去重并校验内容一致。
heading_strict 行为被 TABLE Block 改变 benchmark 语料不再对齐。 heading_strict 使用独立 heading-only 解析路径。
单行超过 max_tokens 无法同时满足完整行和 token 硬上限。 保留完整行,设置 oversized_row;后续可增加 strict/error 策略。
明确不在本次范围: XLS、HTML table、Excel 样式、合并单元格还原、隐藏行列、公式对象、精确单元格坐标和 Markdown 到原始电子表格的反向映射。

12. 一次性交付与验收标准

单一交付单元

所有代码、依赖、测试和文档使用一个完整提交交付,建议提交信息: feat: add markitdown-backed table ingestion and chunking。 不拆分 converter、chunker、index provenance 等中间提交作为独立交付物。

验证命令

uv lock
uv sync --package zleap-sag --extra dev --extra tables

uv run --project packages/sag pytest \
  tests/unit/test_markdown_table_parser.py \
  tests/unit/test_markdown_table_chunker.py \
  tests/unit/test_table_source_chunk_assembler.py \
  tests/unit/test_markitdown_table_conversion.py \
  tests/unit/test_table_pipeline_e2e.py \
  tests/unit/test_table_index_provenance.py \
  -q

uv run --project packages/sag pytest -m "not integration"
uv run --project packages/sag ruff check .
uv run --project packages/sag ruff format --check .
uv run --project packages/sag mypy
uv build --package zleap-sag

Definition of Done

  • Markdown 管道表格能识别为 TABLE Block。
  • CSV/XLSX 能通过 MarkItDown 自动转成 Markdown。
  • XLSX 多 Sheet 能识别为多个独立表格。
  • 每个表格 Chunk 都是完整合法 Markdown。
  • 长表拆分后每个 Chunk 都重复表头。
  • 数据行不丢失、不重复、不乱序。
  • 表格不与正文或其他表格混合。
  • 超长单行有明确可观察策略。
  • 表头只持久化一次并被多个 Chunk 共享。
  • heading_strict 原有语义保持不变。
  • 普通 Markdown 无非预期回归。
  • 缺少可选依赖时给出安装提示。
  • 单测、lint、格式、类型和 wheel 构建全部通过。
  • README、中文/英文 API 和 CHANGELOG 已更新。