如果目标是一次性把 PixelUMM 的多图输入和整页文档混排都接进来,常见的结局是报错和低质输出混在一起:分不清是请求体构造错了、模型不接受这种输入组合,还是文档分页把内容切断了。更稳的推进顺序是先把一张图跑成能重复执行、能写入文件、能对照判定的最小链路,再逐层加第二张图、加整页文档。每一步都单独验证,失败时能直接定位到某一层,而不是从头重来。
PixelUMM 的多图与文档混排不建议一次接入。先固定一张样本图,把请求、写入文件、通过判定做成可重复链路;再加第二张图,只观察请求体与返回结构新增了哪部分;最后换整页文档,单独记录分页方式与长度切分。三步各自保留最小复现样本和错误记录,灰度只从失败已能归因的那一步开始。模型实际支持哪些输入组合,以公开说明和自己实测为准,不要预设某组合一定可用。
把单图任务跑成一条能重复执行的最小链路
这一步的唯一目的是得到一个可信基线:后面加第二张图、换整页文档时,所有对比都对着这条链路的结果看。基线不稳定,后面所有“变差了”的判断都没有意义。
脚本骨架建议做成“固定样本 + 固定提示词 + 落盘 + 记录耗时与状态码”四件事,不要在这一步加循环、加队列、加重试策略。下面这段是通用接入骨架,ENDPOINT 和字段名需要换成你实际服务与公开说明里的写法。
# run_single.py —— 单图最小链路骨架
import json, hashlib, pathlib, time, requests
ENDPOINT = "http://localhost:8000/v1/infer" # 换成你的服务地址
SAMPLE = pathlib.Path("samples/one_page_form.png") # 固定样本,别随手换
OUT = pathlib.Path("runs"); OUT.mkdir(exist_ok=True)
def run_single(img_path, prompt):
payload = {
"input": [{"type": "image", "path": str(img_path)}],
"prompt": prompt,
}
tag = hashlib.md5((img_path.name + prompt).encode("utf-8")).hexdigest()[:8]
out_file = OUT / ("single_" + tag + ".json")
t0 = time.time()
try:
r = requests.post(ENDPOINT, json=payload, timeout=60)
rec = {"status": r.status_code, "elapsed": round(time.time() - t0, 2),
"req": payload, "resp": r.json()}
except Exception as e:
rec = {"status": "exception", "error": repr(e), "req": payload}
out_file.write_text(json.dumps(rec, ensure_ascii=False, indent=2))
return out_file
if __name__ == "__main__":
print(run_single(SAMPLE, "逐行输出图中文字,保持原有阅读顺序"))
样本图要选一张自己熟悉的:文字量中等、有表格线、有一两处空格或勾选框,这样输出质量好坏一眼能看出来。同一张样本图连续跑几次,结果都写入 runs 目录,不要覆盖同名文件,便于回看差异。
单图链路的通过标准建议写成可人工核对的条件,而不是打分数:请求正常返回、返回结构里有你需要的文本字段、图片中的主要文字行没有整行缺失、阅读顺序与人工判断一致、同一张图重复跑几次结果大体稳定。只要“整行缺失”或“顺序明显错乱”出现,就先别往下走,先确认是样本问题还是接口问题。
在单图链路上加第二张图,观察请求与返回结构变化
加第二张图的目的是搞清楚两件事:请求体里哪些字段是新增的,返回里哪些结构是必须自己维护的,比如图与图之间的顺序、每段输出属于哪张图。不要在这一步同时改提示词或换样本图,否则变化来自哪里说不清。
请求体变化通常只是 input 从一项变成两项,但是否需要额外字段(序号、页号、分组标记)要按公开说明来。可以先试最朴素的双图请求:
{
"input": [
{"type": "image", "path": "samples/form_a.png"},
{"type": "image", "path": "samples/form_b.png"}
],
"prompt": "分别输出每张图的文字,不要合并成一段"
}
返回结构对照时重点看三处:单图时返回的是单个文本块还是带条目的列表;双图时是否出现按图分组的标记字段(具体字段名以公开说明为准,不要自己猜一个名字写进代码);当返回只有一段文本时,顺序是否与提交顺序一致。如果返回把两张图的内容合并成一段且没有边界标记,那么“哪段属于哪张图”就得由你的代码自己维护,这属于必须自己承担的部分,不是接口缺陷。
这一步的通过条件是:两张图各自的关键文字都能在输出里找到、能按图区分归属、顺序稳定。做不到按图区分,就不要继续叠加文档输入。
换成整页文档输入,记录分页与长度带来的差异
文档类输入和图片类输入的问题性质不一样。图片的问题多在识别质量和版面理解,文档的问题多在你自己的切分方式上:一页怎么变成一张图、一份多页文档怎么变成多次请求、每段输入的页数或字符数是多少。这三件事不记录,后面出问题只能靠猜。
分页方式通常有两条路:把每页渲染成一张图逐页提交,或者用现成的文档加载方式自动切块。逐页渲染可控性强、便于对照单图链路的结果,建议先用这条;自动切块省事,但切在哪里、切了几块不透明,出问题时不好归因。
长度切分要留下记录,字段至少包括:样本文件名、总页数、本次提交的页码区间、单页图像的边长或分辨率、提示词版本。失败样本的具体表现也要写下来,常见几类:靠后页面内容整段缺失、跨页表格在切分处断行错位、请求超时、返回文本在中途被截断、多页内容顺序与页码不一致。这几种现象对应的排查方向完全不同,混在一起记会反复踩同一个坑。
把三步产生的错误信息归到同一张记录表
三个环节的错误长得像,但原因不同:单图报错多半是请求构造或环境问题,多图报错多半是组合方式或结构假设问题,文档报错多半是切分与长度问题。同一张表里用“出现环节”一列区分,用“复现所需最小样本”一列保证别人能重跑。
| 错误类型 | 出现环节 | 最小复现样本 |
|---|---|---|
| 请求构造错误 | 单图 | one_page_form.png + 原始 payload |
| 鉴权或连接失败 | 单图 | 同一条命令,仅换 ENDPOINT |
| 多图归属不清 | 双图 | form_a + form_b 两张固定图 |
| 内容缺失或缺页 | 文档 | 某一页单独渲染后的图 |
| 超时或返回截断 | 文档 | 同一页区间,减少页数重跑 |
记录条目建议直接用同一份 JSON 结构,方便后面筛选和比对:
{
"sample_id": "doc_p3_p7",
"stage": "doc",
"error_type": "content_missing",
"minimal_repro": "samples/doc_12p.pdf 第 3-7 页逐页渲染",
"observed": "第 6 页表格后半段没有出现在输出中",
"next": "第 6 页单独提交,确认是切分问题还是识别问题"
}
判断标准很简单:同一个错误如果在表里出现两次以上、且“最小复现样本”一栏写不出具体文件或页码,说明这条记录还不够用,先补样本再继续排查。
根据记录表决定哪一步可以先灰度
推进决策从表里读,不从感觉里读。单图环节如果已经能重复执行、没有归类不明的失败,就可以作为灰度起点;双图环节只有在“按图区分归属”这一条稳定做到之后才考虑放量;文档环节只要有未归因的缺页或截断,就先只做内部验证,不扩大范围。
- 灰度准入条件:该环节的失败都能在记录表里落到具体错误类型和最小复现样本;上一环节的基线仍然可复现,没有因为改动而失效。
- 灰度范围:先固定样本集、限定少量账号或内部调用方,只保留当前环节,不同时开文档混排;每类样本都要有人在看输出。
- 回退到上一步的触发信号:出现新的、无法归类的错误类型;同一份固定样本的输出与基线差异明显且原因说不清;文档环节缺页数量无法用切分方式解释。出现任一条,就退回上一环节,先修基线再谈放量。
这套顺序的价值不在流程好看,而在于任何一次失败都能回答三个问题:在哪一层坏的、用哪个最小样本能重跑、下一步该改请求还是改切分。多图和文档混排迟早要接,但接入顺序决定了你是在排查一个问题,还是在排查一堆互相掩盖的问题。