PDF 记录的是文字的图形。它记下每个字形在页面上的位置,至于这些字形背后的字符能否还原,取决于文件是怎样生成的。语言模型基于收到的文本推理,而对泰语文档来说,它收到的文本通常以两种方式之一出错。即使有文本层,其中泰语字符的排列顺序也没有人会这样书写。扫描件则根本没有文本,让模型去读图像,它只能猜测几个像素高的符号。
文件和提示词之间还有一步读取。做好这一步,要先知道模型从泰语 PDF 收到什么,以及为什么要先用专为泰语页面打造的识别器读取。接着要决定交给模型什么格式、如何处理表格和页面边界,以及如何让读取只付一次费。示例用 Paxa OCR Lite 读取,这条流水线的结构适用于任何返回结构的识别器。
模型从 PDF 中收到什么#
把 PDF 发给模型 API 时,服务会替您做两件事之一,却不告诉您做的是哪一件。它要么提取文本层放进提示词,要么把页面渲染成图像放进提示词。处理英文合同时,两条路径都还过得去。换成泰语合同,两条路径都会失败,原因与泰文书写系统本身有关。
泰语文本层由生成软件放置的字形构成。泰语字体把声调符号和上元音的移位副本存为私用区字形,用来避开 ป、ฝ 这类高尾辅音。文本层若引用这些字形,提取出来的就是任何文本工具都不认识的码位。另一些生成软件则把符号写在它所附着的字母之前。文本提取文章整理了这些失败情况。下面是一行印刷文字,以及文本层把它变成的两种结果。
printed ปุ๋ยอินทรีย์ ถุงละ ๒๕๐ บาท text layer A ปยอินทรีย ถงละ ๒๕๐ บาท text layer B ป๋ ุยอินทรีย ์ ถ ุงละ ๒๕๐ บาท
第一种文本层丢掉了字体存为私用字形的所有符号。第二种保留了符号,但其中两个顺序错了。无论读哪一种,模型收到的都是拼不成词的字符串。模型往往能根据周围的字母把词重建出来,所以它读到的并不像乱码,这反而更糟。模型会用最可能的词填补空缺,而在发票上,最可能的词就是一个看似合理的错误数字。
为什么泰语扫描件要先经过识别器再交给模型#
图像路径绕开了文本层,直接面对文字本身。一行泰文在垂直方向上分四层。元音可以挂在辅音下方,元音或 ั 可以位于辅音上方,声调符号再叠在更上面。一列最多叠三个字形,而承载意思的符号正是页面上最小的东西。四个声调符号 ่ ้ ๊ ๋ 和上元音 ิ ี ึ ื,在常规扫描分辨率下只靠几个像素宽的一笔或一点来区分。
通用模型在读取图像之前,会把它缩放到固定的像素预算内,整页泰文缩到这个预算后,声调符号剩下的信息就太少了。模型读出较大的辅音,再推断较小的符号。结果读起来是通顺的泰语,却在只差一个符号的词上与页面不符。ข่าว 和 ข้าว 就是这样一对,金额栏里还有十几对。
用泰语页面训练的识别器按页面的分辨率读取,一次输出字符。模型擅长的是文本。把页面读取一次、再把文本交给模型,能让两个系统各自做擅长的事。读取结果也因此成为一份存储下来的产物,人可以打开核对。
用 Markdown 作为交换格式#
让识别器输出 Markdown。POST /v1/ocr 默认按阅读顺序,把每一页返回为 GitHub 风格的 Markdown。标题以标题标记呈现,列表以列表项呈现,表格以竖线表格呈现。结构由普通字符承载,放进提示词后原样保留,而模型读过的 Markdown 比其他任何结构化文本都多。下面是识别器返回的一页报价单。
# ใบเสนอราคา เลขที่ QT-2569-0142 วันที่ 15 กรกฎาคม 2569 | รายการ | จำนวน | ราคาต่อหน่วย | รวม | | --- | --- | --- | --- | | ปุ๋ยอินทรีย์ 25 กก. | 40 ถุง | 250 | 10,000 | | ค่าขนส่ง | 1 | 800 | 800 | ยืนราคา 30 วัน นับจากวันที่ในใบเสนอราคา
模型需要的内容都在这一页上,除此之外什么也没有。没有几何信息、字体名称、边界框,也没有需要在提示词里解释的置信度分数。表格就是表格,问到合计时,模型能在某个列标题下的单元格里找到合计。用空行把各页连起来就还原了文档,这份文档本身就是提示词。
连接各页之前,值得再加两样东西。用 <!-- page 3 --> 这样的注释标出每页的边界,Markdown 渲染器会隐藏它,模型则把它当作标记读取,这样模型就能在回答中引用页码。另外,保留印刷时的泰文数字。模型能正确读出 ๒๕๐,而展示识别器文本的引用应当与扫描件一致。搜索索引文章讲了检索索引在原文之外还需要的规范化副本。
表格、重复页眉与页面边界#
一张表格是一个整体,应当完整地交给模型。扫描时跨两页断开的竖线表格,会变成只有一个表头的两张表。如果第二页开头的行上方没有表头,流水线就应当把两张表接起来。表格宽到提示词难以承受,是 Markdown 格式与模型相冲突的唯一情况,后文的结构化输出正是为此准备的。
页眉和页脚在每一页重复出现。文档编号、公司名称、页码和保密声明每页都来一次,第一次之后就不再提供新信息。找出在大多数页面顶部或底部反复出现的行并删掉,页码则保留在边界标记里,在那里它起到引用的作用。
长文档要分段读取。一个请求最多读取 50 页、10 MiB,一份 200 页的手册需要 4 个请求。各段的页码要连续编号,因为回答引用的是页码,某一段若从第 1 页重新开始,第一段之后的所有引用都会出错。
只读取一次,只为读取付一次费#
无论一页上有什么内容,每页都是 6.5 积分。那份 200 页的手册需要 1,300 积分,即 $1.30,只要把读取结果存起来,以后对它提出的所有问题都只花这一笔。如果流水线在每个请求里都重新读取文档,每个问题都要再付一次。用文件字节的哈希作为缓存键,同一份文档上传两次也只读取一次。
key = sha256(bytes)
pages = cache.get(key)
if pages is missing:
pages = POST /v1/ocr { document: base64(bytes), model, output: "markdown" }
cache.put(key, pages)
text = join(pages, page => "<!-- page " + page.page + " -->\n" + page.markdown)
answer = model(system, text, question)把识别器的输出与源文件哈希存在一起,每次都用存储的文本重建提示词。页数在扣费前读取,识别器打不开的文档会被拒绝,不扣费。扣费后失败的请求会退还积分。读取时带上 Idempotency-Key,超时后的重试会重放已存储的结果,不会第二次扣费。OCR 指南说明了计费规则。
读取结果外层的提示词#
读取结果就是文档。包在它外面的提示词规定模型可以拿它做什么,其中有三条规则对泰语文档比对英文文档更重要。
- 只根据文本回答,文本中没有答案时要说明。模型一旦转而依赖它对泰国发票的一般了解,就会编出一个看似合理、页面上却从未出现的字段。
- 原样引用泰文。被要求做摘要的模型会规范拼写、删掉语气词,而与页面差一个符号的引文,审核人员在原文里就找不到。
- 数字和纪年保持印刷原样,换算另起一行。๒๕๖๙、2569 和 2026 是同一年份的三种写法,模型若在原处换算,引用指向的文本就不再与页面一致。
You are answering questions about a Thai document. The document follows, one page per <!-- page N --> marker. Answer only from the document. When the answer is absent, say so. Quote Thai text exactly as written, and name the page marker it came from. Keep digits, amounts, and years as printed. Put any conversion on a separate line.
文档照片走同一条路径。一张图片算一页,除 PDF 外,识别器还接受 PNG、JPEG 和 WebP,手机拍的收据照片和其他页面一样是 6.5 积分。拍下整页,识别器就会以与扫描件相同的 Markdown 返回其中的文字。
用于提取的结构化块#
把 output 设为 "structured",每一页就会按阅读顺序以带类型的块返回。标题带有层级,列表带有列表项,表格带有由单元格组成的行。提取字段的流水线可以按类型分发每个块。表格交给代码,代码按列名读取单元格,不需要模型。段落连同其上方的标题路径一起交给模型作为上下文。图片只带说明文字,别无其他。
由此得出一条值得明说的规则:表格块中的每个数字都交给代码处理。代码读得准确,模型只是大概率读对。让模型去做代码做不到的事,比如判断三个段落中哪一段写明了付款条件,或者某个条款约束的是买方还是卖方。按 schema 校验返回的结果,并保留答案所在的块作为引用。
宽表格是使用块的另一个理由。把每一行展开为由列名和值组成的一行键值对。用竖线表示时会折行到无法辨认的表格,就变成了一个列表,模型读取时不会弄混哪个值属于哪一列。
模型仍然发挥作用的地方#
识别器把页面变成文本,其余的交给模型。它可以总结一份五十页的法规,跨一整个文件夹的合同回答问题,根据首页给文档分类。它能把泰语文档书写日期的四种方式统一成一个字段,也能起草一封回信。这些都是文本任务,每一项的效果都取决于它起步时的文本。