ADR-0012: Doclib Block Locator
历史决策记录:保留当时的背景、状态和设计。当前接口与实现归属以 开发入口 和现行规范为准;文中的历史模块路径不保证仍存在。
状态: Accepted 日期: 2026-06-15 相关文档: 0011-doclib-doc-short-id.md, ../middle-json/agent-gaps.md, ../middle-json/structured-content-schema.md, ../workflows.md
背景
Agent 读取文档后,需要稳定地引用文档中的具体内容块。引用必须同时满足:
- 对 Agent 和开发者可读。
- 可从引用回查到 doc、tier、page 和 block。
- 不泄露本地绝对路径或 filename。
- 不依赖 render 输出是否隐藏某些 block。
- 能在 Markdown marker、Structured Content 和 citation record 中复用。
此前部分文档把稳定引用描述成额外 ID。本 ADR 将 P0 的主引用模型收敛为局部 locator 与全局 block reference。
决策
P0 定义两级定位:
- 局部 locator:
page:{page_no}/block:{block_no}
- 全局 block reference:
doc:{short_id}/tier:{tier}/page:{page_no}/block:{block_no}
示例:
page:1/block:3
doc:ab12cd3/tier:basic/page:1/block:3
字段规则:
| 字段 | 说明 |
|---|---|
short_id |
docs.short_id,见 ADR-0011。 |
tier |
实际解析档位: flash / basic / standard / advanced。 |
page_no |
1-based 页号。 |
block_no |
1-based block 号。 |
请求页选择与稳定块引用
read_content() 请求定位器的 page: 部分复用解析输入页范围语法:单页数字、rN、区间、逗号组合和 all。例如 page:r1 是最后一页,page:r3-r1 是最后三页,page:1,3,r1 是第一、第三及最后一页。倒数页和 all 必须依据文档总页数求值,不从部分缓存推算。
求值、排序、去重和越界裁剪复用 DocVortex 公共页范围算法。响应定位器及生成的 block reference 始终使用实际 1-based 页码;ContentCursor.page_no 保持整数,不把范围加入稳定块引用协议。带 /block:、/char: 或图片读取时,选择必须解析为单页;多页选择不接受非零 context。
范围截断时,next_request 保留规范化页范围定位器并携带绝对块/字符 after,供下一次 read_content(locator, after=...) 使用。续读只访问原范围内尚未完成的内容,结束后不推荐范围外页面。已有数字单点定位及单点读取的续读行为保持不变,不新增 page:last 别名。
Block 编号来源
block_no 来自 Middle JSON 中 block 自身的稳定编号,或 canonical Middle JSON page 内的稳定 block 序列。
核心规则:
block_no不是 render 阶段生成的。block_no不是“第几个可见输出块”。- 即使某个 block 在某种输出格式中不显示,也仍然有编号。
- header / footer / page_number 参与编号。
- locator 指向原始结构中的 block,而不是 Markdown / Structured Content 中第几个可见 item。
因此,不同 render profile 不能因为隐藏 block 而重编号。
Render Profile
block_ref 不包含 render profile:
doc:{short_id}/tier:{tier}/page:{page_no}/block:{block_no}
原因:
- 编号在原始 / canonical Middle JSON 中已经存在或可稳定生成。
- 编号不依赖 Markdown、Structured Content、HTML 等 render 输出。
- 输出格式是否隐藏 header/footer/page_number 不影响 block_no。
如果未来 schema major version 改变导致 locator 规则不兼容,应通过 schema migration、schema version 或后续补充字段处理,而不是在 P0 block_ref 中加入 profile。
Span / Cell / List Item
P0 不定义 span、cell 或 list-item locator。
原因:
- P0 以 block 为最小公开引用单元。
- span/cell 不单独暴露为公开 citation 目标。
- OCR span 粒度、表格 cell 合并、Office list 嵌套路径等差异会显著增加复杂度。
后续如需引用 list item 或其他嵌套 child,可在 P1+ 增加:
page:{page_no}/block:{block_no}/child:{child_path}
但这些不是 P0 契约。
与 short_id 的关系
block_ref 的跨文档唯一性依赖 docs.short_id UNIQUE。
short_id 的职责是让 doc reference 短、稳定、可读;block locator 的职责是定位到该 doc 的具体内容块。二者分层:
- ADR-0011 定义
docs.short_id如何生成、持久化和保持稳定。 - 本 ADR 定义如何使用
short_id、tier、page_no、block_no组成 block reference。
完整 sha256 仍应在需要严格校验的结构化响应顶层返回。block_ref 不替代 sha256。
输出形态
Structured Content
Structured Content item 可包含:
{
"locator": "page:1/block:3",
"block_ref": "doc:ab12cd3/tier:basic/page:1/block:3",
"page": 1,
"block": 3,
"type": "paragraph"
}
Markdown Locator Marker
Markdown 可选 locator marker 可包含:
<!-- mineru:block {"ref":"doc:ab12cd3/tier:basic/page:1/block:3","locator":"page:1/block:3","type":"paragraph"} -->
规则:
- 默认 Markdown 可以不输出 block marker。
- Agent / SDK 可显式开启。
- marker 中不得包含本地绝对路径或 filename。
Citation Record
Agent citation record 可包含:
{
"block_ref": "doc:ab12cd3/tier:basic/page:4/block:12",
"locator": "page:4/block:12",
"page": 4,
"block": 12,
"bbox": [10.0, 20.0, 300.0, 160.0],
"bbox_known": true,
"type": "table",
"text": "visible excerpt...",
"source_sha256": "...",
"source_short_id": "ab12cd3"
}
回查
doc:{short_id}/tier:{tier}/page:{page_no}/block:{block_no} 回查流程:
- 使用
short_id精确查找docs.short_id。 - 使用
tier选择对应解析结果。 - 使用
page_no定位 page。 - 使用
block_no定位该页 block。
P0 可以不支持短于 short_id 的 prefix lookup。调用方传入不完整 doc 前缀时,可以返回 doc_ref_not_found 或后续定义的 ambiguous_doc_ref。
替代方案
方案 A: 使用 0-based page/block
拒绝。0-based 与内部 page_idx 一致,但对 Agent 输出、CLI、日志和人类调试不直观。P0 public locator 使用 1-based。
方案 B: 只给可见输出 block 编号
拒绝。这样 locator 会依赖 render profile。隐藏 header/footer/page_number 或切换输出格式时,block 编号可能变化。
方案 C: 在 block_ref 中加入 render profile
拒绝。P0 block 编号不由 render 产生,因此不需要 profile。加入 profile 会让引用更长,也会误导调用方认为 locator 依赖输出格式。
方案 D: P0 暴露 span/cell/list-item locator
拒绝。P0 不把 span/cell/list item 作为公开引用目标。细粒度定位后续再设计。
方案 E: 使用额外 hash ID 作为主引用
拒绝作为 P0 主方案。P0 主引用使用可读的 block_ref,避免同时维护两套含义接近的引用 ID。
影响
- Middle JSON normalization 必须确保每页 block 编号稳定。
- header/footer/page_number 等默认隐藏块仍参与编号。
- render 层不得因隐藏 block 而重编号。
- Structured Content 和 Markdown marker 应复用同一 locator helper。
block_refhelper 应依赖docs.short_id、实体 tier、1-based page_no 和 1-based block_no。
后续动作
- 定义 helper:
locator_for_block(page_no: int, block_no: int) -> str
block_ref(short_id: str, tier: Tier, page_no: int, block_no: int) -> str
- 确认 Middle JSON 中 block 稳定编号字段来源。
- 在 Structured Content 输出中增加
locator/block_ref。 - 为 Markdown 增加可选 block marker。
- 增加回查测试:
- 1-based locator 正确。
- hidden block 参与编号。
- 同一 doc/tier/page/block 生成稳定 block_ref。
- 不同 doc 的相同 page/block 因
short_id不同而不冲突。