先别急着接生产——PixelUMM 的多模态边界得自己验一遍

文章导读
PixelUMM 能不能直接接生产,答案不在模型介绍里,而在你们自己的样本里。低质图、长文档、多图混排这三类输入,恰好是多模态模型最容易给出“看着对、用不了”结果的地方。在没跑过自己这批边界样本之前把流量切过去,等于把兜底责任交给了一个你还不清楚失败模式的组件。下面这套流程要交付的只有几样东西:一份来自真实流量的边界样本清单、一个可复跑的批量脚本、一套失败分类口径、每类失败对应的兜底动作,以及兜底
📋 目录
  1. 一 列出会进生产的真实输入分布
  2. 二 搭一个批量跑样本的调用脚本骨架
  3. 三 把失败按类型归档
  4. 四 为每类失败写一条兜底规则
  5. 五 用兜底后的流程重跑同一批样本
A A

PixelUMM 能不能直接接生产,答案不在模型介绍里,而在你们自己的样本里。低质图、长文档、多图混排这三类输入,恰好是多模态模型最容易给出“看着对、用不了”结果的地方。在没跑过自己这批边界样本之前把流量切过去,等于把兜底责任交给了一个你还不清楚失败模式的组件。下面这套流程要交付的只有几样东西:一份来自真实流量的边界样本清单、一个可复跑的批量脚本、一套失败分类口径、每类失败对应的兜底动作,以及兜底上线后用同一批样本做的复核结果。

先固定一批来自真实流量的边界样本(低分辨率、倾斜、多页、图文混排),用批量脚本跑一遍并原样保存返回;把失败按输入、解析、模型输出三类归档,每类失败配一条能在代码里判定的兜底动作;最后接入兜底后用同一批样本重跑,未通过样本单独列出。只有兜底规则在这批样本上确实生效,再谈把 PixelUMM 接进生产流程。

列出会进生产的真实输入分布

样本来源必须是线上真实请求,不是自己挑的干净图。可以先从网关或业务日志里导出最近一段时间的原始输入,成功和失败的都要,按调用方分别抽样,避免某一家渠道的图占满整批样本。

分组维度建议至少覆盖四层:来源(App 拍照、扫描仪导出、网页上传、邮件附件、接口对接方)、分辨率(按短边分档,例如低、中、高三档,具体分界按你们实际分布定)、版面(单页纯文字、单页表格、票据、多栏、多页文档、图文混排)、语言(中文、英文、中英混排、其他)。

每一组都要给出大致占比,占比来自日志统计,不靠印象估。占比小的类别并不意味着可以跳过,低频但高价值的输入往往是最先出问题的那批。样本量上,每类至少要能看出重复出现的失败模式,量太小的类别结论只能当线索,不能作为兜底规则的依据。

样本清单落到一个 manifest 文件里,字段包括:sample_id、file_path、page_range、source、resolution、layout、lang、task、prompt_version、expected_fields。最后两列很关键,没有它就没法判断模型输出到底是漏了还是本来就不该有。

先别急着接生产——PixelUMM 的多模态边界得自己验一遍

搭一个批量跑样本的调用脚本骨架

骨架的目标是同一批样本能被反复跑、跑出来的结果能原样复现。端点、密钥都从环境变量注入,不要写进脚本;具体请求实现留一个函数空位,按你们接入的 SDK 或 HTTP 客户端替换,脚本本身不关心接口路径长什么样。放在能访问内网的机器上执行,输出目录每次运行单独建一层,避免覆盖上一轮结果。

import csv, json, os, time, pathlib

ENDPOINT = os.environ['PIXELUMM_ENDPOINT']   # 端点由环境变量注入,不写死
KEY = os.environ['PIXELUMM_KEY']
OUT_DIR = pathlib.Path(os.environ.get('OUT_DIR', 'runs/raw'))

def call_pixelumm(payload, timeout=90):
    '''替换成你们实际的调用实现,这里只约定输入输出。'''
    # 返回原始响应体,不要在这里做字段提取或纠错
    raise NotImplementedError

def build_payload(row):
    '''row 来自 manifest.csv,图片和 PDF 走同一条构造逻辑。'''
    return {
        'file': row['file_path'],
        'page_range': row.get('page_range') or None,
        'task': row['task'],                 # 例如 doc_parse / table_extract / mixed_layout
        'prompt_version': row['prompt_version'],
    }

def run(manifest_path):
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    with open(manifest_path, newline='', encoding='utf-8') as f:
        rows = list(csv.DictReader(f))
    for row in rows:
        sample_id = row['sample_id']
        record = {'sample_id': sample_id, 'input': row}
        t0 = time.time()
        try:
            resp = call_pixelumm(build_payload(row))
            record['status'] = 'ok'
            record['raw_response'] = resp        # 若为 bytes,先转成可序列化形式
        except Exception as e:
            record['status'] = 'exception'
            record['error'] = repr(e)            # 异常同样落盘,不要只打日志
        record['elapsed_s'] = round(time.time() - t0, 3)
        with open(OUT_DIR / f'{sample_id}.json', 'w', encoding='utf-8') as g:
            json.dump(record, g, ensure_ascii=False, indent=2)

if __name__ == '__main__':
    run('manifest.csv')

重跑前要冻结 prompt_version、页数参数和超时时间,否则两次结果不可比。原样保存返回是硬要求:只存提取后的字段,后面就没有证据判断失败发生在哪一层。

把失败按类型归档

失败只分三类,口径按“最早发生且不可逆的环节”判定,同一失败只归一类,不允许一条样本同时挂两个标签。

  • 输入问题:文件本身不可用,包括损坏、全黑、短边过低导致文字不可辨认、倾斜角度过大导致页面边缘无法定位、加密或无文本层。这类失败与模型无关,重跑多少次都一样。
  • 解析问题:切分与定位出错,包括多栏串行、表格跨页断裂、图注与图片错位、多页文档页序颠倒。模型可能答得很流畅,但输入给它的内容已经错了。
  • 模型输出问题:字段漏、字段值错、结构不符合约定 schema、字段之间自相矛盾,或对图中不存在的内容给出确定结论。

举个判定例子:低分辨率导致漏字段,归输入问题而不是模型输出问题;页面清楚但表格串行,归解析问题;输入和切分都正确而结论错,才归模型输出问题。每条记录包含样本ID、失败类型、一句话描述、是否可复现、判定人。每类失败各多少条要写清楚,某类只有零星几条时,只能作为观察点,先不要为它单独加规则。

为每类失败写一条兜底规则

兜底规则要写成“触发条件 → 动作 → 返回给调用方什么”三段式,触发条件必须能由代码自动判定,不能靠人看。

先别急着接生产——PixelUMM 的多模态边界得自己验一遍
  • 输入问题:触发条件是短边低于阈值或清晰度评分低于阈值。动作是拒绝处理,不消耗模型调用,返回带明确错误码的响应,提示重拍或重传。阈值先沿用你们旧流程里已有的参数,跑完再调。
  • 解析问题:触发条件是多页文档页序置信度低、版面切分置信度低。动作是降级到旧流程,或者只返回已确定页码的结果并显式标注“不完整”,不要把缺失页静默跳过。
  • 模型输出问题:触发条件是必填字段缺失、schema 校验不通过、关键字段互相矛盾。动作是转人工队列,或返回空值加需复核标记,不允许用相近内容填充。

三类规则的分工要明确:输入问题在入口拦,解析问题在流程中间降级,模型输出问题在出口兜底。这样出问题时,日志能直接定位到是哪一层触发的。

用兜底后的流程重跑同一批样本

同一批样本、同样的输入顺序,把兜底判定接进流程后重跑。对比字段包括:首次是否失败、失败类型、兜底是否触发、触发后执行了哪个动作、最终返回是否可用(可用/不可用/需人工)、调用方拿到的是结果还是错误码。

通过标准建议这样定:所有首次失败的样本都必须命中至少一条兜底规则,不允许出现“失败了但没有任何兜底、还返回了内容”的静默通过;兜底后的返回里不允许出现伪造字段;进入人工队列的样本量要落在你们能承受的范围内,这个范围由业务方定,不由测试脚本定。

未通过的样本要单独列一张表,包括三类:兜底未触发的、兜底动作执行了但返回内容仍不可信的、同一输入多次重跑结论不一致的。对这些样本只有三种处理方式——补一条兜底规则、修正输入分布假设、或者明确这类输入不进入该流程。三种都选不出来,就说明 PixelUMM 在这类输入上还不具备上线的条件。