doc-extract 是做什么的?
文本提取本身就能处理大多数文档。本技能使用快速的本地引擎 进行文本提取,然后只在提取器明确遗漏的地方——图形、扫描页和 未能结构化的表格——调用视觉模型。
支持 .pdf、.docx、.xlsx、.pptx 和图像文件。格式由内容决定,
而非扩展名。
何时不应使用
- 合并、拆分、表单填写、加密 →
anthropics/skills@pdf。 - 只需要从文档中取出一个数字,且已经知道页码 → 直接读取即可。
需要 uv。除此之外不需要其他依赖;依赖在首次运行时自动解析。
1. 转换
uv run <skill-dir>/convert.py FILE [MORE ...]
uv run <skill-dir>/convert.py FILE --inline # 描述放在图片所在位置
每个文档输出一个 JSON 对象到标准输出。所有确定性操作已完成: 文本已提取、图像已写入、产物已缓存。如果任意文档失败,退出码为非零。
{"status":"ok","artifact":"/home/…/<sha>-<tag>","cached":false,
"doc_md":"…/doc.md","pages_dir":"…/pages","manifest":"…/manifest.json",
"pending":[{"id":"p007-render","page":7,"kind":"page_render",
"reason":"dense_grid","path":"…/images/p007-render.png"}],
"dropped":6,"over_scale_guard":false,"scale_guard":15}
先检查 status。encrypted、unreadable 和 unsupported 是终止状态——
报告并跳过;批次继续处理。重新运行同一文件会立即返回 cached: true,
不消耗任何成本。
如果 over_scale_guard 为 true,告知用户它需要多少次调用,
在继续之前获得同意。84 页的扫描件就是 84 次调用,40 张图片的幻灯片也是如此。
2. 查看每个待处理项目
pending 为空 → 完成,转到第 3 步。
否则,对于每个条目:读取 path 处的文件,然后写入你看到的内容:
uv run <skill-dir>/describe.py <artifact> <id> "你的描述"
# 或者通过管道输入长描述: … describe.py <artifact> <id> -
如何编写描述取决于 reason——参见 reference/describing-visuals.md:
reason | 要写什么 |
|---|---|
standalone_raster、curves、diagonals | 描述图形:类型、展示内容、坐标轴和单位、显著值、所有可读文本。 |
no_text_layer | 逐页转录文本。 这是 OCR 路径;完全不存在文本。 |
dense_grid、stroke_grid | 提取器无法结构化的表格。将其重现为 Markdown 表格。 |
raster_grid | 由许多图片平铺而成的页面——组合图,或按条带存储的单张照片。描述各面板共同展示的内容,然后说明每个面板的显著之处。 |
describe.py 可以安全地重新运行——它会替换而非重复,因此中途中断的视觉传递可以简单恢复。
kind: "native_chart" 的项目永远不会出现在 pending 中。
这些是电子表格图表,其系列数据已从图表定义中读取,因此数字是精确的,
而非从像素估算。
3. 回答
- 整个文档:
doc.md。 - 特定问题:按单位文件搜索,只读取需要的部分。
- 按单位引用,或在多个文档前加上文件名前缀
(
[report.pdf:p12])。
| 格式 | 单位 | 引用方式 | 单位文件 |
|---|---|---|---|
| 页 | [p12] | pages/p012.md | |
| PowerPoint | 幻灯片 | [s07] | pages/u007.md |
| Excel | 工作表 | [Sheet2] | pages/u002.md |
| Word | 一级标题 | [Budget assumptions] | pages/u003.md |
| 图片 | 整个文件 | [img] | pages/u001.md |
Office 单位文件按编号而非名称命名,因为名为 Q1 P&L / draft 的工作表
不是有效的文件名。manifest.json 包含一个 units 列表,
将每个文件映射到其标签,按顺序排列。
唯一规则
永远不要手动编辑 doc.md。 你添加的所有内容都通过 describe.py 完成,
它会用分隔符包裹内容。这允许基准测试剥离这些添加的部分,
证明该技能不会降低文本提取质量。直接编辑会静默破坏这一保证。
描述放置的位置,以及 --inline 不保证的一件事
默认情况下,所有描述以一块的形式附加在 doc.md 末尾,
标记为 [s02]、[p12] 或 [Sheet2]。--inline 则将每个块放在
其图片所在位置。在转换时传递该参数,而非 describe.py:
放置位置记录在产物中(及其缓存键中),因此恢复的视觉传递
不会混淆两者。
| 格式 | --inline 可以锚定到 |
|---|---|
| PowerPoint | 图片所在的行(当该行可证明是该图片的行时);否则放在幻灯片末尾 |
| Word | 图片所在节的末尾——anydoc 会将图片渲染为其替代文本,而 Word 除非作者输入过替代文本,否则通常不写,因此通常没有行可锚定 |
| Excel | 工作表的末尾——图片和图表来自包文件,anydoc 两者都不渲染 |
| PDF、图片 | 无。--inline 对 PDF 没有影响 |
该块是插入在引擎行的旁边,而非替换该行,这就是为什么字节一致性得以保持——
eval/gate.py 会运行两种放置方式,并在任何原地编辑时失败。
内联不保证位置是正确的;字节一致性无法检查放置位置,
因为插入无论落在哪里都能往返。锚定是 anydoc 的替代文本,
它只是普通文本:一张关于名为 image.png 的文件的幻灯片
会渲染出与图片相同的行。因此,仅当该单位中候选行的数量等于其中图片数量时,
才使用图片自身的行;否则描述回退到单位边界。位置要么可证明是那张图片的,
要么是单位边界——绝不会在两条看似相似的行之间猜测。
为什么过滤器是现在的样子
不要“简化”这些——每个过滤器存在的原因是在真实文档上测试过明显的替代方案但失败了:
| 过滤器 | 原因 |
|---|---|
| 丢弃超过 50% 单位、小于 120px、宽高比 >8:1 的图片 | 一份 14 页的拨款 PDF 有 69 个图片位置:7 个不同的对象,其中六个是徽标、规则和侧边栏条纹。 |
| 跳过已生成 Markdown 表格的页面 | 提取器对于它能解析的表格比视觉模型更好。仅在其未产生结果时才介入。 |
| 要求在两个方向上都有笔画 | 下划线和规则仅是水平方向。计算所有轴对齐的笔画会在参考书目页面和带有下划线标题的合同上触发。 |
将 diagonals 排除在面积下限之外 | 正文中不会出现 4 个以上的对角线线段,且面积下限会否决放置在页面角落的图表。 |
| 密集网格分支上的墨水阈值 | 将提取器遗漏的带阴影表格与装饰性章节横幅区分开。 |
| 将超过 6 个光栅的页面合并为一次渲染 | 一个 48 块的修复对比图是一个整体,一张 TI 包装照片以 12 条带形式出现;逐块调用消耗约 2 倍的标记数且丢失了构图。有 5-6 个光栅的页面有时是不同的图形,因此界限设为 6。 |
| 丢弃 EMF、WMF 和 OLE 负载 | 文本引擎忠实保留它们,但如果没有光栅化器则无法读取——将其路由到 pending 会创建一个任何智能体都无法完成的项目。计入 dropped,从不静默丢弃。 |
harvest.py 是 PDF 路由的唯一权威来源,filters.py 是适用于所有格式的部分。
README 和设计规范中的每个数字都从它们重新生成;永远不要手动编辑数字。
如果你无法执行某个文件,reference/harvest-block.md 是一个可复制粘贴运行的自包含副本。
Office 路由故意更精简,这是诚实的
PDF 页面是一个隐藏图形的绘图指令程序,这就是为什么 harvest.py 从矢量几何推断它们。
OOXML 在包中声明其图片,因此无需推断。Office 文档获得装饰性过滤器、
引用和缓存契约以及此规则——但没有 render_reason、没有 raster_grid
也没有 cost_guard,因为没有渲染引擎就无法渲染幻灯片或工作表。