CodeArts Agent【精准视觉识别PDF文档转译为Markdown】【提示词】

举报
luichun 发表于 2026/10/16 11:19:43 2026/10/16
【摘要】 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 崩溃后重启):

  1. 检查 D:/example/temp/meta.json 是否存在;若存在,再检查 failed_pages 是否非空(meta.json 在 render.py 末尾写入,不存在可能说明渲染中途崩溃,图片已部分渲染但需重跑步骤1)。若 failed_pages 非空,按强制要求6"续跑与 failed_pages 交互"处理(打印列表并终止)
  2. 检查 D:/example/output.md 最大页码 N(grep -o '^## 第 [0-9]* 页$' D:/example/output.md | grep -o '[0-9]*' | sort -n | tail -1)
  3. 若 N > 0 且 N < total_pages → 进入续跑模式,先执行步骤2加载schema,再从步骤3第 N+1 页开始识别
  4. 若 N = total_pages → 转译已完成,直接进入步骤6校验(不重新识别)
  5. 若 N = 0 或 output.md 不存在 → 从步骤1重新开始
  6. 若 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 清空,按正常续跑流程执行
  • 后续每批用 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 插图引用格式

![描述文字](pdf_images/pageX_imgY.png)

命名规则: 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 编造的未出现在原文中的文字)
  • 抽样页无截断(段落/列表项完整)
  • 抽样页无乱码

术语校验

  • 信息安全语境下"泄露"统一用字(信息泄露、数据泄露、密码泄露、密钥泄露、权限泄露、凭证泄露、源码泄露、配置泄露)
  • 物理渗漏语境下保持"泄漏"(管道泄漏、油泄漏、水泄漏、气体泄漏)
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。