CodeArts Agent【精准视觉识别PDF文档转译为Markdown】【提示词】
PDF 页面渲染视觉识别转译 Markdown 通用提示词
平台声明:本流程基于 CodeArts Agent 平台。若移植其他平台,按下表替换等价工具:
CodeArts 工具 等价替换说明 tool_describe(id="analyzeImage")加载该平台图像分析工具的 schema tool_call(id="analyzeImage", args={...})调用该平台图像分析工具 write/bash/read该平台的文件写入/命令执行/文件读取工具 运行环境:Windows 11 + git-bash。依赖命令:
cat/rm/grep/ls/diff/seq/python/sort/wc/awk/xargs/basename/echo。grep须支持-o(不依赖 PCRE-P)。若纯 PowerShell/CMD,替换为等价命令。所有文件编码 UTF-8 无 BOM,换行符 LF。使用方式:
- 作为一次性提示词:将本文件内容完整粘贴给 AI,替换"输入参数"中的路径即可执行。
- 作为 skill:当用户提供 PDF 路径并要求"转译 Markdown"时触发。输入:源 PDF 路径、目标 md 路径、页面图目录、插图目录、运行模式。输出:转译后的 Markdown 文件 + 校验报告(即步骤6的 stdout 输出,含页码/插图/CRLF/failed_pages 等校验结果,不落盘)。
定位声明:本提示词只保证"流程可执行、异常可标记、结果可校验"。PDF 内容千差万别,具体表格、公式、多栏、扫描质量、标签体系、术语表等,不在通用提示词中穷举;遇到无法可靠识别的内容,一律标记后交人工复核。
中断恢复流程(会话中断或 AI 崩溃后重启):
- 检查
D:/example/temp/meta.json是否存在;若存在,再检查failed_pages是否非空(meta.json 在 render.py 末尾写入,不存在可能说明渲染中途崩溃,图片已部分渲染但需重跑步骤1)。若failed_pages非空,按强制要求6"续跑与 failed_pages 交互"处理(打印列表并终止)- 检查
D:/example/output.md最大页码 N(grep -o '^## 第 [0-9]* 页$' D:/example/output.md | grep -o '[0-9]*' | sort -n | tail -1)- 若 N > 0 且 N < total_pages → 进入续跑模式,先执行步骤2加载schema,再从步骤3第 N+1 页开始识别
- 若 N = total_pages → 转译已完成,直接进入步骤6校验(不重新识别)
- 若 N = 0 或 output.md 不存在 → 从步骤1重新开始
- 若
temp/footnote_counter.txt存在且 N > 0 → 续跑兼容(保留计数器);N = 0 → 重跑重置(删除计数器)长度说明:全文以
wc -l统计为准(当前约 550 行)。若上下文窗口有限,可将"禁止事项""避坑指南""质量检查清单"移至附录按需加载,核心流程(步骤1-6)常驻。
输入参数(使用时替换为实际绝对路径)
源 PDF 路径(绝对路径): D:\example\source.pdf
目标 Markdown 路径(绝对): D:\example\output.md
页面渲染图目录(绝对路径): D:\example\images\ ← PDF每页转PNG存放于此
预截取插图目录(绝对路径): D:\example\pdf_images\ ← 用户手动截取的插图;若无预截取插图,填"无"
运行模式: 重跑 ← "重跑"=从第1页开始覆盖写入;"续跑"=从断点追加
路径规则:
- 所有路径统一使用绝对路径
- Python 代码中用原始字符串
r"..."或双反斜杠\\ - shell 命令中用正斜杠
/ tool_call的args(JSON)中用双反斜杠\\(JSON 转义)- 临时文件目录:
D:\example\temp\(目标 Markdown 同级目录下的temp\)
任务目标
将指定 PDF 文档完整转译为 Markdown 文档。必须完全使用页面渲染+视觉模型识别方式:逐页将 PDF 页面渲染为 PNG 图片,用视觉模型识别图片中的文字和布局,输出为 Markdown 格式。每页标记页码,插图引用对应图片文件。
"页面渲染+视觉识别"定义:
- ✅ 允许:用 PyMuPDF(fitz)将 PDF 页面渲染为图片(
get_pixmap),再用视觉模型(analyzeImage)识别图片 - ❌ 禁止:用 PyMuPDF(fitz)提取文本(
get_text),或用 pdfplumber、Tesseract 等外部 OCR 工具 - ❌ 禁止:直接用 AI 平台的文件读取工具(如
read)读取 PDF 并依赖平台自动解析文字——这属于"文本提取"非"视觉识别",准确度与排版均不可靠 - 视觉模型内置的文字识别能力属于"视觉识别",不属于"外部 OCR 工具",允许使用
强制要求(必须遵守)
1. 识别方式:仅限页面渲染 + 视觉模型识别
- 必须将 PDF 每页渲染为 PNG 图片,用视觉模型识别图片中的文字和布局
- 禁止使用 PyMuPDF
get_text()、pdfplumber、Tesseract 等工具直接提取文本 - 禁止直接用
read工具读取 PDF 依赖平台自动解析文字(属于文本提取,非视觉识别) - 禁止混合使用文本提取和视觉识别
- 允许使用 PyMuPDF
get_pixmap()渲染页面为图片,这不属于"文本提取"
2. 工具调用:必须通过 tool_call
analyzeImage是 deferred built-in tool,必须通过tool_call(id="analyzeImage", args={...})调用- 禁止直接调用
analyzeImage(...)(即在工具调用中写analyzeImage(imagePath=..., task=...)) - 如何区分:
tool_call(id="analyzeImage", args={...})是正确调用;analyzeImage(...)是直接调用(错误) - 首次使用前先调用
tool_describe(id="analyzeImage")加载 schema
3. analyzeImage 工具 schema
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
imagePath |
string[](数组) |
与imageId/imageBase64三选一 | 本地图片绝对路径列表,支持多图 |
imageId |
string[](数组) |
与imagePath/imageBase64三选一 | 已上传图片的ID列表 |
imageBase64 |
string[](数组) |
与imagePath/imageBase64三选一 | base64编码图片数据 |
task |
string |
是 | 分析任务描述 |
返回结构: 返回纯文本/Markdown 格式的识别结果字符串。多图时按图片顺序依次返回,每图结果前有 This is the analysis result of image batch [...] 前缀。
4. task 描述参数
| 场景 | task 描述 | 字符数 | 效果 |
|---|---|---|---|
| 普通文字页 | "识别图片中所有文字,原文输出markdown格式,不要翻译。" |
~25字 | ✅ 大部分页面成功 |
| 图表页/返回空重试 | "请仔细识别这张图片中的所有文字内容,包括每一行文字、标题、正文。原文输出markdown格式,不要翻译,不要遗漏任何文字。" |
~55字 | ✅ 图表页/复杂页成功 |
| 过长描述 | >100字 | >100字 | ❌ 返回空结果 |
task 拼接规则: 特殊页面(多栏/旋转/竖排)在基础 task 上追加指令。拼接优先级:基础 task → 旋转提示 → 多栏提示 → 竖排提示。拼接后总长不超过 100 字;若超限,省略低优先级提示,仅保留基础 task + 最高优先级提示。
详细 task 重试规则: 详细 task(~55字)仅用于普通文字页返回空时重试。特殊页面返回空时,用基础 task + 最高优先级特殊提示重试,不叠加详细 task(详细 task + 特殊提示易超 100 字导致返回空)。
规则:简短 task 优先;返回空时改用详细 task 重试,最多重试 2 次;重试单独成批(不与下一批识别并行),仍为空则按"返回空处理"流程判断。
5. 并行调用限制
- 每批最多 3 个并行
tool_call - 同一响应中并行工具调用总数(含
tool_call、bash、write、read等)不超过 6 个 - 跨响应等待识别结果时不计入并行限制
tool_describe不计入并行限制(仅首次调用一次)- 重试单独成批:某页返回空需重试时,在下一响应中单独发送该页的
tool_call,不与下一批新页并行 - 禁止在同一个响应中同时调用
tool_call(id="analyzeImage")和直接调用analyzeImage - 禁止多个
bash cat >>并行写同一目标文件,写入必须串行
6. 写入方式:分批追加
- 重跑模式:首次写入用
write创建目标文件(覆盖旧内容),后续追加 - 续跑模式:目标文件已存在,从断点页开始追加写入(不覆盖)
- 断点 N 判定:用
bash读取目标文件中最大的## 第 X 页页码,N = 该页码;若输出为空,显式N=0 - 若目标文件不存在或无页码标记,N = 0,从第 1 页开始
- 续跑只支持连续写入,不支持中间补页:若目标文件存在缺页(如 1,2,4,5 缺第3页),续跑从 N+1 开始,不会补中间缺页。中间缺页需人工检查或重跑该页
- 续跑与 failed_pages 交互:续跑时 render.py 会重渲染缺失页,failed_pages 可能更新。但续跑断点 N 不受 failed_pages 影响(仍从 N+1 开始)。若 failed_pages 非空,续跑前打印失败页列表并终止,提示用户手动处理(AI 是自动执行者,不等待人工决定)。用户处理完后重新执行步骤1(重渲染,更新 meta.json)+ 步骤3(从断点 N+1 续跑);若重渲染后 failed_pages 清空,按正常续跑流程执行
- 断点 N 判定:用
- 后续每批用
write创建临时文件,用bash的cat >>串行追加到目标文件 - 禁止一次性写入所有内容
- 每批识别 3 页、写入 3 页(识别一批立即写入一批,不跨批缓存)。"写入 3 页"指识别页 3 页;失败页标记额外占用页块,按页码排序合并写入,不视为违反"写入 3 页"
- 临时文件放在
D:\example\temp\,命名:temp_<起始页>_<结束页>.md(如temp_004_006.md) - 重试时同名临时文件直接覆盖
- 完成后清理所有临时文件(含
temp_*.md、meta.json、pages.txt、expected.txt、referenced.txt、available.txt、comment_pages.txt、footnotes.txt;保留footnote_counter.txt供续跑分配编号) - 断点续跑:若中途失败,目标文件已写入前 N 页。续跑时从第 N+1 页开始识别和追加写入
7. 逐页标记
- 每页必须标记:
<!-- 第 X 页 -->+## 第 X 页+ 内容 +--- - 禁止跳过页码标记或合并多页为一个标记
- 原文标题层级统一降一级(见下表),避免与
## 第 X 页冲突
| 原文层级 | 目标层级 | 说明 |
|---|---|---|
# |
### |
降一级 |
## |
#### |
降一级 |
### |
##### |
降一级 |
#### |
###### |
降一级 |
##### |
###### + <!-- 原五级标题 --> |
降一级,超过六级加注释 |
###### |
###### + <!-- 原六级标题 --> |
已是最低级,加注释标记 |
完整流程
步骤 1:PDF 转图片(页面渲染)
将以下 Python 脚本写入 D:\example\temp\render.py,通过 bash 执行:
bash(command="python D:/example/temp/render.py")
render.py 内容(用 write 工具创建):
import os
import json
try:
import fitz
except ImportError:
print("ERROR: PyMuPDF(fitz)未安装")
print("执行: pip install PyMuPDF")
exit(1)
pdf_path = r"D:\example\source.pdf"
img_dir = r"D:\example\images"
temp_dir = r"D:\example\temp"
try:
os.makedirs(img_dir, exist_ok=True)
os.makedirs(temp_dir, exist_ok=True)
except Exception as e:
print(f"ERROR: 无法创建目录: {e}")
print("请检查路径是否有效(驱动器是否存在、路径是否可写)")
exit(1)
try:
doc = fitz.open(pdf_path)
except Exception as e:
print(f"ERROR: 无法打开PDF: {e}")
exit(1)
if doc.needs_pass:
if not doc.authenticate(""):
print("ERROR: PDF已加密,无法解密")
exit(1)
total_pages = len(doc)
zero_width = max(4, len(str(total_pages)))
failed_pages = []
for i, page in enumerate(doc):
out_path = os.path.join(img_dir, f"page_{str(i+1).zfill(zero_width)}.png")
if os.path.exists(out_path):
continue
try:
mat = fitz.Matrix(2.0, 2.0)
pix = page.get_pixmap(matrix=mat)
pix.save(out_path)
except Exception as e:
print(f"警告: 第 {i+1} 页渲染失败: {e}")
failed_pages.append(i+1)
doc.close()
with open(os.path.join(temp_dir, "meta.json"), "w", encoding="utf-8") as f:
json.dump({"total_pages": total_pages, "zero_width": zero_width, "failed_pages": failed_pages}, f)
print(f"总页数:{total_pages},补零位数:{zero_width}")
if failed_pages:
print(f"警告: {len(failed_pages)} 页渲染失败: {failed_pages}")
print("这些页图片缺失,识别时将按'返回空处理'流程标记")
执行前确认: 先用 bash(command="ls D:/example/temp/render.py") 确认文件创建成功,再执行 python D:/example/temp/render.py。若 write 失败导致 render.py 不存在,不执行 python。
异常处理:
- PyMuPDF 未安装:脚本打印提示,AI 执行
pip install PyMuPDF后重试 - PDF 加密:脚本尝试空密码解密,失败则报错终止,已生成文件保留(不清理)
- 权限不足:报错终止,已生成文件保留,提示用户检查路径权限
- 目录创建失败:脚本打印提示,终止并报告,不继续步骤 2
- 页面渲染失败:脚本记录失败页到 meta.json 的 failed_pages,继续渲染其他页;识别时失败页按"返回空处理"流程标记
- 退出码检查:若 bash 返回非 0,终止并报告错误,不继续步骤 2
步骤 2:加载 analyzeImage schema
tool_describe(id="analyzeImage")
步骤 3:分批识别图片
识别前先读 failed_pages:用 bash 读取 meta.json 中的 failed_pages 列表。该列表中的页图片不存在,直接标记"(本页渲染失败,需人工复核)"并写入目标文件,不调用 analyzeImage,避免无效调用。
# 读取失败页列表
FAILED=$(python -c "import json; print(' '.join(map(str, json.load(open('D:/example/temp/meta.json'))['failed_pages'])))" 2>/dev/null)
echo "渲染失败页: $FAILED"
失败页与批次整合规则:
- 失败页不占用识别槽位:识别批次只包含图片存在的页
- 失败页标记单独生成(不调用 analyzeImage),不占用识别槽位;写入时与识别结果合并按页码升序排序后统一写入
- 例:总5页,第2页渲染失败 → 识别批次为"第1,3,4页"+"第5页";第2页生成失败标记
- 写入顺序:失败页标记与识别结果合并按页码升序排序后统一写入(如第1页识别+第2页失败标记+第3页识别 → 按1,2,3顺序写入),保持页码连续
每批 3 个并行 tool_call,识别一批写入一批:
# 第 1 批(第 1-3 页)
tool_call(id="analyzeImage", args={
"imagePath": ["D:\\example\\images\\page_0001.png"],
"task": "识别图片中所有文字,原文输出markdown格式,不要翻译。"
})
tool_call(id="analyzeImage", args={
"imagePath": ["D:\\example\\images\\page_0002.png"],
"task": "识别图片中所有文字,原文输出markdown格式,不要翻译。"
})
tool_call(id="analyzeImage", args={
"imagePath": ["D:\\example\\images\\page_0003.png"],
"task": "识别图片中所有文字,原文输出markdown格式,不要翻译。"
})
返回空的处理流程(按顺序执行):
1. 改用详细 task 重试(最多 2 次,单独成批)
2. 仍为空 → 用 read 工具读取该 PNG 图片
- read 支持图片(CodeArts 平台):返回图片供 AI 视觉判断
- read 不支持图片(其他平台 fallback):跳到步骤 4
3. AI 视觉判断(4种分支):
a. 纯图表页且 pdf_images 有对应插图 → 引用 pdf_images/pageX_imgY.png
b. 纯图表页且无插图 → 标记"(本页为图表页,未能识别文字内容)"
c. 空白页 → 标记"(本页为空白页)"
d. 有文字但识别失败 → 标记"(本页识别失败,需人工复核)"
4. AI 也无法判断(兜底)→ 标记"(本页识别失败,需人工复核)"
步骤 4:格式化识别结果
4.1 通用格式规则
| 原始格式 | 目标格式 | 判别条件 |
|---|---|---|
[标签] |
- 【标签】 |
仅限以下已知标签:[安全措施]、[保护对象]、[云平台原生安全能力满足度]、[云安全产品满足度分析]、[云服务客户自身安全能力建议]、[合规满足度]。其他方括号不转换 |
| 页眉行 | 标记候选 | 页内可判定:位于页面视觉顶部第1行、且与正文之间有明显分隔(横线/空白/字号差异)→ 加 <!-- 候选页眉 --> 注释标记。通用提示词不自动删除页眉,只标记候选,交人工处理。章节标题仅全文档首次出现时保留 |
| 页脚行 | 标记候选 | 页内可判定:位于页面视觉底部最后3行内(版本号、版权声明、单独页码行)→ 加 <!-- 候选页脚 --> 注释标记。通用提示词不自动删除页脚,只标记候选,交人工处理 |
泄漏 |
泄露 |
信息安全语境下替换。判别规则:该词所在句子/段落涉及"信息、数据、密码、密钥、权限、凭证、源码、配置、隐私、秘密"等词时,泄漏→泄露 |
泄漏 |
泄漏 |
物理渗漏语境保持不变:管道泄漏、油泄漏、水泄漏、气体泄漏 |
4.2 布局元素转换规则
| 布局元素 | 转换规则 | 失败处理 |
|---|---|---|
| 表格 | 转为 Markdown 表格 \| 列1 \| 列2 \| + 分隔行 \|:---\|:---\| |
合并单元格用HTML <table>;无表头时首行作表头;多级表头合并为一行 |
| 多栏排版 | 在 task 中加"按从左到右、从上到下阅读顺序输出" | 若交错输出,在格式化时手动按栏合并 |
| 公式 | 保持原文 LaTeX 或 Unicode 表示 | 识别不清时标记 <!-- 公式识别不清 --> |
| 代码块 | 用 ```language 包裹 |
嵌套代码用4空格缩进;代码中含 ``` 时用4个以上反引号包裹 |
| 脚注 | 转为 [^N] 引用 + 全文档末尾放 [^N]: 内容 |
编号全局唯一(不按页重置),N 从1递增。分批写入下的完整处理流程见下方"脚注缓存规则表" |
| 超链接 | 仅当 URL 文字本身在图片中可见时,转为 [文本](URL) |
URL 不可见时,保持原文文字,不编造链接目标 |
| 竖排文字 | 识别后按从上到下顺序输出,加 <!-- 竖排文字 --> 标记 |
— |
| 旋转页 | 在 task 中加"图片可能旋转,请按正常阅读方向识别" | — |
| 图片说明(图注) | 图注作为插图引用的 ![描述文字] 部分,不单独转斜体 |
若图注与图片分离(不在图片旁),转 *图注*(斜体) |
脚注缓存规则(分批写入下脚注处理完整流程):
| 步骤 | 操作 | 命令/规则 |
|---|---|---|
| 1. 识别脚注 | 每批识别到脚注时,正文写 [^N] 引用 |
N 从全局计数器读取 |
| 2. 暂存定义 | 脚注定义 [^N]: 内容 追加到 temp/footnotes.txt |
bash(command="echo '[^N]: 内容' >> D:/example/temp/footnotes.txt"),串行追加 |
| 3. N 全局唯一 | 用 temp/footnote_counter.txt 存当前最大 N |
每批识别前读计数器,分配 N 后更新;首批若文件不存在则 N=1 |
| 4. 追加到文末 | 作为固定步骤执行(步骤5全部写入完成后、步骤6校验前),不依赖"最后一批"判定 | bash(command="cat D:/example/temp/footnotes.txt >> D:/example/output.md") |
| 5. 追加后清空 | 追加完成后清空 footnotes.txt(不是删除),避免续跑时重复追加 | bash(command="> D:/example/temp/footnotes.txt")(清空内容,文件保留) |
| 6. 无脚注时 | footnotes.txt 为空或不存在 |
跳过追加,不报错 |
| 7. 续跑兼容 | footnote_counter.txt 不清理(供续跑分配编号);footnotes.txt 追加后已清空(续跑只含新定义) |
续跑前:`N=$(cat temp/footnote_counter.txt 2>/dev/null |
| 8. 重跑重置 | 重跑模式开始前必须重置计数器,否则新文档脚注从旧 N+1 开始,导致 [^1]~[^N] 悬空 |
bash(command="rm -f D:/example/temp/footnote_counter.txt"),重跑首次写入前执行 |
| 9. 文件保留 | footnote_counter.txt 排除在清理之外;footnotes.txt 正常清理(已清空) |
步骤6清理时保留 footnote_counter.txt,删除 footnotes.txt |
4.3 每页标记格式
<!-- 第 X 页 -->
## 第 X 页
(页面内容)
---
4.4 插图引用格式

命名规则: pageX_imgY.png 中 X = 页码(不补零,如 page1_img1.png,与用户手动截取命名一致),Y = 该页第几张插图(从1递增)。
描述文字来源: 优先从识别结果中提取图片附近的图注/图题;若无图注,用页面上下文概括图片类型(如"流程图"、"架构图"),这属于"概括类型"不算"猜测具体内容细节"。"猜测具体内容细节"指编造图中不存在的文字/数据/关系。
4.5 跨页内容处理
| 跨页类型 | 处理方式 |
|---|---|
| 普通段落 | 截断处不加特殊标记,下一页开头续上剩余内容 |
| 跨页表格 | 每页独立,不回填起始页。起始页放表格已识别部分,结束页放续表部分并加 <!-- 续上页表格 --> 标记 |
| 跨页列表 | 每页独立,每页列表项各自编号,不跨页续编序号 |
| 跨页代码块 | 每页独立,每页用 ``` 包裹各自部分,加 <!-- 代码续上页 --> 标记 |
原则:逐页识别、逐页写入,不跨页回填已写入内容。
步骤 5:分批追加写入
# 重跑模式 - 首次写入(第 1 批,第 1-3 页)
# 重跑前重置脚注计数器,避免上次运行的编号残留导致悬空
bash(command="rm -f D:/example/temp/footnote_counter.txt")
write(filePath="D:\\example\\output.md", content="...第1-3页格式化内容...")
# 续跑模式 - 首次写入(从断点 N+1 开始)
# 先用 bash 读取断点:
# bash(command="N=$(grep -o '^## 第 [0-9]* 页$' D:/example/output.md | grep -o '[0-9]*' | sort -n | tail -1); N=${N:-0}; echo $N")
# N=${N:-0} 确保空输出时 N=0
# 分支 1: N=0(目标文件不存在或无页码标记,等同重跑)
# 重置脚注计数器(等同重跑)
bash(command="rm -f D:/example/temp/footnote_counter.txt")
# 用 write 创建 output.md
write(filePath="D:\\example\\output.md", content="...第1-3页格式化内容...")
# 分支 2: N>0(目标文件已有前 N 页,追加写入)
# 禁止用 write 覆盖 output.md(否则前 N 页丢失)
# 示例(假设 N=3):实际执行时 temp 文件名须用实际 N 计算,如 N=7 则 temp_008_010.md
write(filePath="D:\\example\\temp\\temp_004_006.md", content="...第4-6页格式化内容...")
bash(command="cat D:/example/temp/temp_004_006.md >> D:/example/output.md")
# 后续追加(每批,N>0 时继续用临时文件+cat >>)
write(filePath="D:\\example\\temp\\temp_007_009.md", content="...第7-9页格式化内容...")
bash(command="cat D:/example/temp/temp_007_009.md >> D:/example/output.md")
写入必须串行:每批写入完成后再写入下一批。
注意:清理在步骤6校验之后执行(校验会产生 pages.txt 等中间文件,提前清理会导致校验失败)。
续跑模式前置条件:必须先执行步骤1(重新生成 meta.json 和缺失的页面图片)。上次运行清理后 meta.json 和 render.py 已删除,步骤6校验依赖 meta.json 获取总页数。若图片已全部存在,render.py 可跳过已渲染页(脚本中加文件存在检查);若需补渲染缺失页,重新创建 render.py 并执行。
步骤 6:最终校验
# 0. 校验 render.py 未调用 get_text()(清理前检查)
if [ -f "D:/example/temp/render.py" ]; then
grep -n "get_text" "D:/example/temp/render.py" && echo "错误: render.py 调用了 get_text()" || echo "render.py 未调用 get_text() (正确)"
else
echo "警告: render.py 不存在(可能已被清理),跳过 get_text() 校验"
fi
# 读取总页数(容错)
TOTAL=$(python -c "import json; print(json.load(open('D:/example/temp/meta.json'))['total_pages'])" 2>/dev/null)
if [ -z "$TOTAL" ]; then
echo "警告: meta.json 不存在或损坏,请手动输入总页数"
TOTAL=0
fi
echo "总页数: $TOTAL"
# 1. 校验页码标记数(精确匹配 ^## 第 X 页$)
COUNT=$(grep -c "^## 第 [0-9]* 页$" "D:/example/output.md")
echo "页码标记数: $COUNT / 总页数: $TOTAL"
# 2. 校验页码连续性
grep -o "^## 第 [0-9]* 页$" "D:/example/output.md" | grep -o "[0-9]*" | sort -n > D:/example/temp/pages.txt
if [ "$TOTAL" -gt 0 ]; then
seq 1 $TOTAL > D:/example/temp/expected.txt
diff D:/example/temp/expected.txt D:/example/temp/pages.txt && echo "页码连续" || echo "页码不连续"
fi
# 3. 校验注释与页码标记一致性(X 值配对)
grep -o "^<!-- 第 [0-9]* 页 -->$" "D:/example/output.md" | grep -o "[0-9]*" | sort -n > D:/example/temp/comment_pages.txt
diff D:/example/temp/comment_pages.txt D:/example/temp/pages.txt && echo "注释与页码一致" || echo "注释与页码不一致"
# 3.1 校验注释与标题相邻(注释行下一行应为标题行)
ADJACENT_COUNT=$(grep -A1 "^<!-- 第 [0-9]* 页 -->$" "D:/example/output.md" | grep -c "^## 第 [0-9]* 页$")
echo "注释-标题相邻数: $ADJACENT_COUNT / 页码标记数: $COUNT"
# 4. 校验每页末尾 ---(统计 --- 行数,应等于页数)
SEP_COUNT=$(grep -c "^---$" "D:/example/output.md")
echo "分隔符数: $SEP_COUNT / 页码标记数: $COUNT"
# 5. 插图引用校验(仅当 pdf_images 填了实际路径且目录存在)
# 注意:PDFIMAGES 须与输入参数中的"预截取插图目录"一致,填"无"时设为 PDFIMAGES="无"
PDFIMAGES="D:/example/pdf_images"
if [ "$PDFIMAGES" = "无" ] || [ "$PDFIMAGES" = "none" ]; then
echo "pdf_images 填'无',跳过插图校验"
elif [ -d "$PDFIMAGES" ]; then
grep -o "page[0-9]*_img[0-9]*\.png" "D:/example/output.md" | sort -u > D:/example/temp/referenced.txt
ls "$PDFIMAGES"/*.png 2>/dev/null | xargs -I{} basename {} | sort -u > D:/example/temp/available.txt
echo "已引用插图数: $(wc -l < D:/example/temp/referenced.txt)"
echo "可用插图数: $(wc -l < D:/example/temp/available.txt)"
diff D:/example/temp/available.txt D:/example/temp/referenced.txt
if [ $? -ne 0 ]; then
echo "插图差异说明: diff中 < 行=未引用图片(警告), > 行=引用了不存在的图片(错误)"
fi
else
echo "警告: pdf_images 填了路径但目录不存在: $PDFIMAGES,跳过插图校验"
fi
# 5.1 校验 failed_pages 对应页的标记存在
FAILED=$(python -c "import json; print(' '.join(map(str, json.load(open('D:/example/temp/meta.json'))['failed_pages'])))" 2>/dev/null)
if [ -n "$FAILED" ]; then
for p in $FAILED; do
grep -q "^<!-- 第 $p 页 -->" "D:/example/output.md" && echo "第 $p 页(渲染失败)标记存在" || echo "警告: 第 $p 页(渲染失败)标记缺失"
done
else
echo "无渲染失败页"
fi
# 5.2 页眉页脚候选统计(只统计不删除,交人工处理)
HEADER_CANDIDATES=$(grep -c "<!-- 候选页眉 -->" "D:/example/output.md" 2>/dev/null); HEADER_CANDIDATES=${HEADER_CANDIDATES:-0}
FOOTER_CANDIDATES=$(grep -c "<!-- 候选页脚 -->" "D:/example/output.md" 2>/dev/null); FOOTER_CANDIDATES=${FOOTER_CANDIDATES:-0}
echo "候选页眉数: $HEADER_CANDIDATES, 候选页脚数: $FOOTER_CANDIDATES"
echo "提示: 页眉页脚只标记候选不自动删除,请人工确认后用 sed 删除注释行+内容行"
# 6. 检查换行符(LF,不应有 CRLF)
CR_COUNT=$(awk '{n+=gsub(/\r/,"")} END{print n+0}' "D:/example/output.md")
if [ "$CR_COUNT" -gt 0 ]; then
echo "警告: 文件含 CRLF 换行符 (CR数: $CR_COUNT)"
else
echo "换行符: LF (正确)"
fi
# 6.1 检查 BOM(UTF-8 无 BOM,文件开头不应为 EF BB BF)
BOM_COUNT=$(python -c "print(1 if open('D:/example/output.md','rb').read(3)==b'\xef\xbb\xbf' else 0)")
if [ "$BOM_COUNT" -eq 1 ]; then
echo "警告: 文件含 UTF-8 BOM (需去除)"
else
echo "BOM: 无 (正确)"
fi
# 7. 清理所有临时文件(校验完成后执行;保留 footnote_counter.txt 供续跑分配编号)
rm -f D:/example/temp/temp_*.md D:/example/temp/meta.json D:/example/temp/pages.txt D:/example/temp/expected.txt D:/example/temp/referenced.txt D:/example/temp/available.txt D:/example/temp/comment_pages.txt D:/example/temp/footnotes.txt D:/example/temp/render.py
# 8. 确认清理完成(footnote_counter.txt 保留不报残留)
ls D:/example/temp/ 2>/dev/null | grep -E "temp_.*\.md|meta\.json|pages\.txt|expected\.txt|referenced\.txt|available\.txt|render\.py|comment_pages\.txt|footnotes\.txt" && echo "警告: 有临时文件残留" || echo "无临时文件残留"
echo "保留文件: footnote_counter.txt (供续跑分配编号)"
禁止事项
| 编号 | 禁止行为 | 原因 |
|---|---|---|
| 1 | 禁止用任何方式直接提取 PDF 文本(PyMuPDF get_text()/pdfplumber/Tesseract/read 工具读 PDF 依赖平台解析等) |
格式丢失、中文间隔错误、CID 乱码、排版不可靠 |
| 2 | 禁止直接调用 analyzeImage(...) |
deferred tool,返回 invalid 错误 + doom loop |
| 3 | 禁止同时调用 tool_call 和直接 analyzeImage |
重复结果 + doom loop |
| 4 | 禁止 task 描述超过 100 字 | 返回空结果 |
| 5 | 禁止一次性写入所有内容 | 上下文限制导致内容丢失 |
| 6 | 禁止跳过页码标记 | 无法校验完整性 |
| 7 | 禁止翻译原文 | 任务是转译非翻译 |
| 8 | 禁止合并多页为一个标记 | 破坏页码对应关系 |
| 9 | 禁止猜测图片中不存在的文字/数据/关系 | 必须实际识别 |
| 10 | 禁止残留临时文件(含 meta.json/pages.txt 等) | 完成后必须清理所有中间文件 |
| 11 | 禁止并行写同一目标文件 | 内容顺序错乱 |
| 12 | 禁止将所有 [xxx] 统一转 - 【xxx】 |
会误改引用/链接/代码中的方括号 |
| 13 | 禁止跨页回填已写入内容 | 分批写入无法回填 |
| 14 | 禁止编造不可见的超链接 URL | 渲染图中 URL 不可见 |
避坑指南
doom loop 触发条件与解决
见强制要求 2(工具调用:必须通过 tool_call)和强制要求 4(task 描述参数),不再重复表格。
返回空结果的处理流程
见步骤 3 中的"返回空的处理流程"(完全相同,不再重复)。
特殊页面处理
| 页面类型 | 处理方式 | task 追加 |
|---|---|---|
| 纯文字页 | 正常识别输出 | 基础task |
| 图文混排页 | 文字正常输出,图片引用 pdf_images/ |
基础task |
| 纯图表页(有插图) | 引用 pdf_images/pageX_imgY.png |
详细task |
| 纯图表页(无插图) | 标记 (本页为图表页,未能识别文字内容) |
详细task |
| 空白页 | 标记 (本页为空白页) |
— |
| 扫描页 | 按普通文字页处理;若识别失败,标记"(扫描页识别失败,需人工复核)" | 基础task |
| 旋转页 | 识别后按正常方向输出 | 基础task + "图片可能旋转,请按正常阅读方向识别" |
| 多栏页 | 按阅读顺序合并为单栏 | 基础task + "按从左到右、从上到下阅读顺序输出" |
| 竖排页 | 按从上到下顺序输出,加 <!-- 竖排文字 --> 标记 |
基础task + "竖排文字请按从上到下顺序输出" |
质量检查清单
自动校验项
- PDF 总页数 = md 中
^## 第 [0-9]* 页$标记数 - 页码从 1 连续递增到最后页无跳号无重复(用
seq+diff校验) <!-- 第 X 页 -->的 X 与## 第 X 页的 X 一一对应(用diff校验)<!-- 第 X 页 -->与## 第 X 页相邻(注释行下一行即标题行,用grep -A1校验)- 每页末尾有
---分隔符(^---$行数 = 页数) - 插图引用唯一数与 pdf_images 文件夹做 diff(仅当 pdf_images 填了实际路径)
- diff 中
<行(未引用图片)为警告,>行(引用不存在图片)为错误 - 无临时文件残留(含 meta.json/pages.txt/expected.txt 等所有中间文件)
- 换行符为 LF(无 CRLF)
- 文件无 UTF-8 BOM(文件开头3字节不为 EF BB BF)
- 转译脚本中未调用
get_text()(检查 render.py 源码) - failed_pages 对应页均有"(本页渲染失败,需人工复核)"标记(用
grep逐页校验)
人工抽样校验项(每 20 页抽 1 页)
- 抽样页内容与 PDF 原文一致(无漏识别、错识别)
- 抽样页格式正确(表格、列表、标题层级)
- 抽样页无幻觉内容(AI 编造的未出现在原文中的文字)
- 抽样页无截断(段落/列表项完整)
- 抽样页无乱码
术语校验
- 信息安全语境下"泄露"统一用字(信息泄露、数据泄露、密码泄露、密钥泄露、权限泄露、凭证泄露、源码泄露、配置泄露)
- 物理渗漏语境下保持"泄漏"(管道泄漏、油泄漏、水泄漏、气体泄漏)
- 点赞
- 收藏
- 关注作者
评论(0)